Media Type
上传文件时,需要指定媒体类型,有下面这两种。
multipart/form-data
在HTML中,表单form中的enctype 属性,指定在发送到服务器之前应该如何对表单数据进行编码。
默认地,表单数据会编码为 “application/x-www-form-urlencoded”。就是说,在发送到服务器之前,所有字符都会进行编码(空格转换为 “+” 加号,特殊符号转换为 ASCII HEX 值)。
当表单使用 POST 请求时,数据会被以 x-www-urlencoded 方式编码到 Body 中来传送,而如果 GET 请求,则是附在 url 链接后面来发送。GET 请求只支持 ASCII 字符集,因此,如果我们要发送更大字符集的内容,我们应使用 POST 请求。
enctype 有三种可配置类型:
值 | 描述 |
---|---|
application/x-www-form-urlencoded | 在发送前编码所有字符(默认) |
multipart/form-data | 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。 |
text/plain | 空格转换为 “+” 加号,但不对特殊字符编码。 |
如果要发送大量的二进制数据(non-ASCII),application/x-www-form-urlencoded 显然是低效的,因为它需要用 3 个字符来表示一个 non-ASCII 的字符。因此,这种情况下,应该使用 “multipart/form-data” 格式。
multipart/form-data 定义在 rfc2388 中,最早的 HTTP POST 是不支持文件上传的,给编程开发带来很多问题。但是在1995年,ietf 出台了 rfc1867,也就是《RFC 1867 -Form-based File Upload in HTML》,用以支持文件上传。所以 Content-Type 的类型扩充了multipart/form-data 用以支持向服务器发送二进制数据。
application/octet-stream
application/octet-stream属于HTTP规范中Content-Type的一种,很少使用。
只能提交二进制,而且只能提交一个二进制,如果提交文件的话,只能提交一个文件,后台接收参数只能有一个,而且只能是流(或者字节数组)。
Spring MVC对文件上传的支持
multipart包
在spring-web模块中的multipart包下,提供了很多类和接口用于处理multipart请求。
commons包是对apache中commons-fileupload组件的支持。
MultipartFile接口
MultipartFile接口是多部分请求中接收的上传文件对象。
MultipartFile定义了一些获取上传文件信息、流、操作文件的方法。
public interface MultipartFile extends InputStreamSource {
// 返回multipart form参数的名称
String getName();
// 返回客户端文件系统中的原始文件名,由客户提供,不应盲目使用。
// 建议不要直接使用此文件名。最好生成一个唯一的并保存
String getOriginalFilename();
// 返回文件的ContentType
String getContentType();
// 返回上传的文件是否为空,即没有文件
boolean isEmpty();
// 返回文件的大小(以字节为单位)如果为空,则返回0
long getSize();
// 以字节数组的形式返回文件的内容。如果为空,则返回空字节数组,在访问错误时抛出IOException
byte[] getBytes() throws IOException;
// 读取文件内容返回InputStream,用户负责关闭返回的流
// 如果为空,则返回空流,在访问错误时抛出IOException
InputStream getInputStream() throws IOException;
// 返回此多部分文件的Resource 表示形式。
default Resource getResource() {
return new MultipartFileResource(this);
// 将接收到的文件传输到给定的目标文件,这可以在文件系统中移动文件
// 或将内存中的内容保存到目标文件,如果目标文件已存在,将首先删除它。
//
void transferTo(File dest) throws IOException, IllegalStateException;
// 将接收到的文件传输到给定的目标文件夹
default void transferTo(Path dest) throws IOException, IllegalStateException {
FileCopyUtils.copy(getInputStream(), Files.newOutputStream(dest));
}
}
MultipartResolver接口
MultipartResolver解析器,是用于解析包括文件上传在内的multipart请求。
支持两种方式:
- 基于Commons FileUpload 的实现。
- 基于 Servlet 3.0 multipart 请求解析。
MultipartResolver定义了三个方法:
public interface MultipartResolver {
// 确定给定的请求是否包含multipart 内容
boolean isMultipart(HttpServletRequest request);
// 将给定的HTTP请求解析为多部分文件和参数,并将请求包装在multipartttpServletRequest。
MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
// 清理用于多部分处理的所有资源
void cleanupMultipart(MultipartHttpServletRequest request);
}
MultipartResolver有两个实现类,CommonsMultipartResolver,StandardServletMultipartResolver。
CommonsMultipartResolver是对Apache Commons FileUpload组件的支持。
StandardServletMultipartResolver是MultipartResolver接口的标准实现,基于Servlet3.0,使用时要作为“multipartResolver”类型的bean添加到Spring DispatcherServlet上下文中(spring boot已默认配置)。
public class StandardServletMultipartResolver implements MultipartResolver {
// 是否在执行时延迟解析多部分请求,默认值为“false”,立即解析多部分元素。
private boolean resolveLazily = false;
public void setResolveLazily(boolean resolveLazily) {
this.resolveLazily = resolveLazily;
}
// 检查请求ContentType 是否包含multipart
@Override
public boolean isMultipart(HttpServletRequest request) {
return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
}
// 解析Multipart为MultipartHttpServletRequest
@Override
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}
// 清理
@Override
public void cleanupMultipart(MultipartHttpServletRequest request) {
// others....
}
}
MultipartRequest接口
此接口定义多部分请求对象访问操作。
public interface MultipartRequest {
// 返回一个{@link java.util.Iterator}字符串对象,其中包含
// 此请求中包含的多部分文件的参数名称。这些是表单的字段名(与普通参数类似)
//而不是原始文件名。
Iterator<String> getFileNames();
// 根据返回此请求中上传文件的MultipartFile 对象
MultipartFile getFile(String name);
// 根据返回此请求中上传文件的MultipartFile 对象集合
List<MultipartFile> getFiles(String name);
// 返回此请求中包含的多部分文件的{@link java.util.Map}。
// @返回一个包含作为键的参数名的映射,以及{@link MultipartFile}对象作为值
Map<String, MultipartFile> getFileMap();
// 返回MultiValueMap
MultiValueMap<String, MultipartFile> getMultiFileMap();
// 返回content type
String getMultipartContentType(String paramOrFileName);
}
MultipartRequest有如下实现类:
DefaultMultipartHttpServletRequest是MultipartHttpServletRequest的默认实现,提供生成参数值的相关方法。
StandardMultipartHttpServletRequest包装Servlet 3.0 HttpServletRequest
和它的部分对象。参数通过请求的getParameter公开。
上传文件案例
方式1.使用fileupload
1、 添加一个thymeleaf上传文件页面;
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="zh">
<head>
<meta charset="UTF-8">
<title>文件管理</title>
</head>
<body>
<form name="form1" method="post" action="/file/common/upload" enctype="multipart/form-data">
选择文件: <input type="file" name="file">
<input type="submit" value="点击上传"/>
</form>
</body>
</html>
添加一个跳转thymeleaf页面的控制器。
@GetMapping("/commonView")
public Object commonView() {
return "file/file";
}
1、 添加fileupload工具包;
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
1、 添加一个处理器方法,处理上传文件逻辑;
@PostMapping("/common/upload")
@ResponseBody
public Object commonUpload(@RequestParam CommonsMultipartFile file) throws IOException {
if (file == null || file.isEmpty()) {
return "文件不能为空";
}
try {
// 1. 创建目标文件夹
File dir = new File("D:\\tmp" + File.separator + "files");
if (!dir.exists()) {
// 文件夹不存在时,创建
dir.mkdirs();
}
// 2. 获取文件信息
String contentType = file.getContentType();
System.out.println("contentType========" + contentType);
String name = file.getName();
System.out.println("name========" + name);
String originalFilename = file.getOriginalFilename();
System.out.println("originalFilename========" + originalFilename);
long size = file.getSize();
System.out.println("size========" + size);
Resource resource = file.getResource();
System.out.println("resource========" + resource.toString());
String storageDescription = file.getStorageDescription();
System.out.println("storageDescription========" + storageDescription);
// 3. 创建保存文件对象
String storeFileName = UUID.randomUUID() + "_"+originalFilename; // 文件名采用UUID
File storeFile = new File(dir.getAbsolutePath() + File.separator + storeFileName);
file.transferTo(storeFile);
} catch (Exception e) {
e.printStackTrace();
return "上传失败";
}
return "success";
}
1、 注入CommonsMultipartResolver解析器;
@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
@ConditionalOnMissingBean(MultipartResolver.class)
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setResolveLazily(false);
return multipartResolver;
}
1、 测试;
选择文件,点击上传,返回success,表示上传成功,去目标文件夹,也发现了上传的文件
方式2.使用Spring Multipart解析器上传
1、 注释CommonsMultipartResolver,Springboot已经自动注入了StandardServletMultipartResolver;
2、 编写一个控制器方法,除了访问路径和入参对象不同,其他的处理逻辑和上面一样;
@PostMapping("/spring/upload")
@ResponseBody
public Object springUpload(@RequestParam MultipartFile file) throws IOException {
// 省略。。。
}
1、 在页面添加一个上传文件表单;
<form name="form1" method="post" action="/file/spring/upload" enctype="multipart/form-data">
选择文件form1: <input type="file" name="file">
<input type="submit" value="点击上传"/>
</form>
1、 测试,和上面一样;
文件下载案例
文件下载时,我们只需要将流写入响应报文就可以了。
1、 创建一个下载页面;
<form name="form3" method="post" action="/file/download" enctype="application/x-www-form-urlencoded">
输入文件完整路径: <input type="text" name="filePath">
<input type="submit" value="下载文件"/>
</form>
1、 创建一个下载控制器,直接取流,然后放到响应中;
@PostMapping("/download")
public ResponseEntity<?> download(@RequestParam String filePath) {
try (InputStream in = new FileInputStream(filePath)) {
// 获取文件流
byte[] body = new byte[in.available()];
in.read(body); // 写入到字节数组
String fileName = filePath.substring(filePath.lastIndexOf("\\") + 1); // 截取到文件名
MultiValueMap<String, String> headers = new HttpHeaders(); // 将文件信息写入消息头
headers.add("Content-Disposition", "attachment;filename=" + fileName);
return new ResponseEntity<>(body, headers, HttpStatus.OK);
} catch (FileNotFoundException exception) {
exception.printStackTrace();
return new ResponseEntity<>("服务器FileNotFoundException", HttpStatus.INTERNAL_SERVER_ERROR);
} catch (IOException e) {
e.printStackTrace();
return new ResponseEntity<>("服务器IOException", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
1、 测试;
版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: