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); }