1. 网页客户端使用表单提交文件上传
当使用表单提交文件上传时,必须:
- 表单
<form>必须配置method="post"; - 表单
<form>必须配置enctype="multipart/form-data";
其它部分与常规的表单开发相同。
2. 在控制器中处理文件上传
在处理请求的方法中,添加MultipartFile接口类型的参数,在处理过程中,调用该参数对象的void transferTo(File dest)方法即可保存客户端上传的文件到方法参数File dest对应的位置!
此处使用到的MultipartFile,就是客户端提交的文件数据,被框架封装后的对象,所以,这个MultipartFile包含客户端提交的文件数据,也包含相关的信息。
在保存客户端上传的文件时,可能需要考虑一些必要的问题:
- 文件必须保存在webapp下,否则,即使上传了,也无法被下载,就没有意义了,通过
HttpServletRequest或HttpSession调用getServletContext().getRealPath(String name)就可以获取webpp文件夹或其子级文件夹的真实路径; - 文件名应该规避冲突,避免互相覆盖,例如:使用时间、随机数等组合出文件名,甚至使用UUID作为文件名,具体策略可以自行决定;
- 应该保持与客户端上传时相同的扩展名,通过
file.getOriginalFilename()可以获取文件的原始文件名,即文件在客户端时的文件名,从中就可以截取出扩展名部分,在做这种操作时,需要额外考虑:有些文件是没有扩展名,表现为整个文件全名中没有小数点,或只有第1个字符是小数点(在Linux/MacOS中,使用小数点作为第1个字符表示该文件或文件夹是隐藏的,这个小数点并不是用于分隔文件名和扩展名的)。
3. 关于MultipartFile接口类型的API
在MultipartFile接口类型中,需要使用的API有:
String getOriginalFilename():获取文件的原始文件名,即文件在客户端中的文件名;boolean isEmpty():判断上传的文件是否为空,当用户在上传表单中没有选择文件就直接上传,或选择的文件是0字节,则返回true,否则返回false;long getSize():获取客户端上传的文件的大小,以字节为单位;String getContentType():获取客户端上传的文件的MIME类型,该值是根据文件的扩展名对应的;InputStream getInputStream():获取客户端上传的文件的输入流,通常用于自定义接收客户端上传的文件的数据,例如客户端上传的文件较大时,可以自定义缓冲区大小等存储数据的过程,该方法不要与transferTo()方法同时使用;void transferTo(File dest):将客户端上传的文件数据保存到服务器端的某个文件中,该方法不要与getInputStream()方法同时使用;
4. 关于SpringBoot框架限制上传文件的大小
在一些SpringBoot框架中,默认限制了上传文件的大小为1M,如果尝试上传的文件超过限制值,则出现:
Maximum upload size exceeded; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException:
The field file exceeds its maximum permitted size of 1048576 bytes.
关于自定义上传文件的大小的限制值,有2种做法!
第1种,是自定义配置上传文件大小的对象,可以在启动类中添加:
@Bean
public MultipartConfigElement getMultipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
// 当前项目中,无论上传的是什么,都不允许超过100M,否则直接报错,控制器根本就不执行
DataSize dataSize = DataSize.ofMegabytes(100);
//最大文件大小
factory.setMaxFileSize(dataSize);
//最大请求大小
factory.setMaxRequestSize(dataSize);
return factory.createMultipartConfig();
}
第2种,是在application.properties中添加配置信息即可!SpringBoot框架的版本更新较快,早期版本和现今的主流版本的配置方式是不同的!
在SpringBoot 2.0之后:
spring.servlet.multipart.max-file-size=10MB spring.servlet.multipart.max-request-size=100MB
5. 关于一次性上传多个文件
首先,需要先确定上传的多个文件的数量是否固定,例如某些软件中,需要上传身份证的正面照片和反面照片,就属于是“数量固定”的,且每个上传的文件(照片)的定位是不相同的,则在同一个表单<form>中添加多个上传控件即可,例如:
<form method="post" enctype="multipart/form-data" action="">
<p>
请选择身份证的正面照片:<input type="file" name="file1" />
</p>
<p>
请选择身份证的反面照片:<input type="file" name="file2" />
</p>
</form>
然后,在控制器中,处理请求的方法中,使用2个参数来表示2个不同的文件即可:
public JsonResult<?> upload(MultipartFile file1, MultipartFile file2) {
// 处理第1个文件file1
// 处理第2个文件file2
}
如果上传的文件的数量在一定范围内不可控,例如微信朋友圈,上传的照片最多9张,但是,实际是多少张并不确定!或者网购之后发布买家秀,上传的照片的数量也是不确定的,可以采取的做法有:使用多个同名的上传控件:
<form method="post" enctype="multipart/form-data" action="">
<p>
请选择第1张照片:<input type="file" name="files" />
</p>
<p>
请选择第2张照片:<input type="file" name="files" />
</p>
</form>
以上代码中,使用的多个上传控件的name值是相同的!在前端页面中,还可以使用JS技术动态添加更多的上传控件!
或者,还可以只使用1个上传控件,但是,在上传控件中添加multiple="multiple"属性,则用户在浏览需要上传的文件时,可以按下键盘的Ctrl键,同时选择多个文件!例如:
<form method="post" enctype="multipart/form-data" action="">
<p>
请选择需要上传的文件:<input type="file" name="files" multiple="multiple" />
</p>
<p>
<b>提示:浏览文件时,按住键盘上的Ctrl键可以同时选择多个文件!</b>
</p>
</form>
在服务器端的控制器中,处理这样的请求时,应该将上传文件的对象声明为数组格式,例如:
public JsonResult<?> upload(MultipartFile[] files) {
// 遍历数据,处理参数files中的每一个文件数据
}
6. 在前端页面使用$.ajax()函数实现异步上传
在调用$.ajax()函数实现上传时,与提交普通请求的区别在于:
- 关于
data属性,必须是new FormData(表单); - 另外添加
"contentType":false和"processData":false这2个属性的配置。
7.控制器处理单个文件demo
@PostMapping("avatar/change")
public JsonResult<String> changeAvatar(MultipartFile file,HttpSession session){//不能长传超过1m大小的文件
if(file.isEmpty()) {
System.err.println("文件为空");
throw new FileEmptyException("头像上传失败!请选择有效的投降文件!");
}
if(file.getSize()>maxSize) {
throw new FileSizeException("上传头像失败!文件的大小超出"+(maxSize/1024)+"K!");
}
//判断上传的文件类型是否是允许的类型
if(!typeList.contains(file.getContentType())) {
throw new FileTypeException("上传头像失败!不支持的文件类型!支持的类型有:"+typeList.toString());
}
//确定将客户端上传的文件保存在哪里
//文件夹路径
String parent = session.getServletContext().getRealPath("avatar");
//文件名方案一:使用UUID生成随机文件名,缺点不便于文件排序
String filename = UUID.randomUUID().toString();
//文件名方案二:使用年月日时分秒+纳秒,精确到纳秒所以不会重复,便于排序
String filename2 = sdf.format(new Date())+ System.nanoTime();//System.nanoTime()纳秒时间
//文件后缀
String suffix = "";
//获取原始文件名
String originalFilename = file.getOriginalFilename();
//获取后缀
int index = originalFilename.lastIndexOf(".");
suffix = originalFilename.substring(index);
//拼接为完整文件名
String child = filename2 + suffix;
File dest = new File(parent,child);
//执行保存客户端上传的文件
try {
file.transferTo(dest);
} catch (IllegalStateException e) {
throw new FileStateException("上传头像失败,请检查头像文件是否正常,并再次尝试!");
} catch (IOException e) {
throw new FileIOException("头像上传失败!传输过程出现错误,请稍后再试!");
}
//记录文件存储的位置
String avatar = "/avatar/" + child;
//存储到数据库
Integer uid = Integer.valueOf(session.getAttribute("uid").toString());
String username = session.getAttribute("username").toString();
userService.changeAvater(uid, username, avatar);
return new JsonResult<>(OK,avatar);
}