目录
1.前置maven依赖
这一步最主要的是在maven中导入两个与文件上传相关的依赖(别忘了提前写好SpringMVC的相关依赖)。
XML
<!-- 文件上传依赖 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.1</version>
</dependency>
2.编写springmvc.xml注册文件解析器
在springmvc配置文件中创建一个id值为multipartResolver的bean对象,class指向"org.springframework.web.multipart.commons.CommonsMultipartResolver",并配置其中相关的属性值:
XML
<!-- 文件上传解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 编码,解决中文文件名乱码 -->
<property name="defaultEncoding" value="UTF-8"/>
<!-- 单个文件最大 10MB -->
<property name="maxUploadSizePerFile" value="10485760"/>
<!-- 单次请求所有文件总和最大20MB -->
<property name="maxUploadSize" value="20971520"/>
<!-- 小于2KB存在内存,超过写入临时文件 -->
<property name="fileSizeThreshold" value="2048"/>
<!-- 延迟解析,方便全局捕获上传超限异常 -->
<property name="resolveLazily" value="true"/>
</bean>
如果觉得太麻烦,快速实验:
XML
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--只配置一个限制文件上传大小就行了-->
<property name="maxUploadSize" value="10485760"/>
</bean>
3.在web.xml中配置SpringMVC的前端控制器
这一步如果已经写好了web.xml就不用再配置了:
XML
<!--配置前端控制器,对浏览器发送的请求进行统一处理-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--加载springmvc.xml配置文件的位置和名称,配置的是Spring配置-->
<init-param>
<!--contextConfigLocation:上下文配置路径,固定值-->
<param-name>contextConfigLocation</param-name>
<!--classpath:类路径,值得是Java和resources文件夹-->
<!--springmvc.xml:指的是配置文件的名称:需要配置springmvc.xml,在下面-->
<param-value>classpath:Springmvc.xml</param-value>
</init-param>
<!--配置启动加载-->
<load-on-startup>1</load-on-startup>
</servlet>
以上都是一些和配置相关的代码,下面就可以开始我们的文件上传试验了。
4.编写前后端代码
我们首先在前端创建一个一个简单的input标签上传文件和一个input按钮,并使用jquery类选择器选中按钮:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../js/jquery.js"></script>
</head>
<body>
<div>文件上传实验</div>
<input class="file-value" type="file"><br><br>
<input class="file-input-button" type="button" value="上传">
<script>
$(".file-input-button").on("click",function(){
})
</script>
</body>
</html>
后端在Controller层创建一个接口和问价接收方法设置接口的url为:"input_file",前端使用ajax请求访问后端接口,此时还不要着急传文件到后端。
后端input_file方法的参数类型设置为MultipartFile类型,返回类型设置为String,如果想要设置传递参数码,可以将返回类型设置为HashMap类型。
后端Controller层:
java
@Controller
public class AlmController {
@ResponseBody
@RequestMapping("/input_file")
public String input_file(@RequestParam("uploadFile") MultipartFile uploadFile) {
}
}
回到前端,在"file-input-button"的类选则器中,创建一个file元素,并另file元素指向"file-value"标签中选中的文件:
html
let file = $(".file-value")[0].files[0];
解释:jQuery 选择器,获取页面上 class="file-value" 的 <input type="file"> 标签。
返回的不是原生 DOM 对象,是 jQuery 包装对象。
这个包装对象中并没有我们需要的files属性,所以需要想办法从包装对象中取出原生的DOM对象,然后调用袁尚DOM对象的files属性,才能获取文件完整的内容。
所以第一个索引0表示从jquery包装对象中拿到第一个类名为"file-value"的DOM对象,然后使用这个DOM对象的files属性。然后有泪一个索引0 获取第一个选中文件 File 对象,里面包含:
文件名、大小、后缀、文件二进制数据,现在需要想办法将获得的文件上传到后端,肯定不能是以字符串形式上传的,所以这里还有问题:文件从前端上传到后端的类型是什么类型?
Formdata格式
Formdata是浏览器内置 API,专门用来模拟表单提交,可以携带:普通文本、图片 / 文件、数组参数,自动处理 multipart/form-data 格式,是前端文件上传标准方案。
普通 {key:file} JSON 对象发送时,文件会被转成 object File 字符串,后端拿不到二进制。只有 FormData 能携带二进制流,自动封装成后端 MultipartFile 能解析的请求体。
那我们清楚了,在前端创建Formdata格式对象,将Formdata格式对象返回后端,再由后端的MultipartFile类型数据来接收并使用相关方法获取文件。
前端创建Formdata对象并发起ajax请求:
html
let fd = new FormData();
fd.append("uploadFile", file);
$.ajax({
url:"/input_file",
type:"POST",
contentType:false,// 1. 禁止jQuery自动设置Content-Type,交给FormData自己生成
processData:false,// 2. 禁止jQuery把data转成字符串(FormData不能序列化)
// FormData 本身就是完整请求体载体,data 直接赋值 fd 即可,不要再包一层 {}
data: fd,
//处理请求失败
success:function(res){
alert("上传成功:" + res);
},
error:function(){
alert("上传失败");
}
})
MultipartFile类型数据
MultipartFile是SpringMVC 封装的接口,用来接收前端通过 multipart/form-data 上传的文件。
作用是用来接收浏览器传过来的文件,提供方法获取文件名、文件大小、文件二进制流,还能直接保存到服务器本地。
使用前提: 前端用 FormData 上传文件,后端参数写
@RequestParam("Formdata参数名") MultipartFile xxx。常用方法:
isEmpty():判断有没有传文件
getOriginalFilename():拿到用户本地的文件名
getInputStream():拿到文件数据流
transferTo(File):把文件存到服务器硬盘
MultipartFile对象中的文件信息分两种存储形式,由 fileSizeThreshold 阈值控制:
文件很小(小于阈值)
文件二进制数据直接存内存字节数组,全部放在 MultipartFile 对象里,没有临时磁盘文件。
文件很大(大于阈值)
不会全放内存,浏览器上传后写入服务器临时磁盘文件;
MultipartFile 对象只保存这个临时文件的路径、文件信息,真正的二进制存在硬盘临时文件里,用到时再去磁盘读取。
由此我们可以知晓,MultipartFile对象已经帮助我们保存了文件的相关信息(二进制流的形式),我们现在需要做的就是找到一个文件夹目录最为仓库将MultipartFile对象中的文件信息读取出来,顺便使用UUID给文件重新命名防止文件名发生冲突。
所以后端的思路大致就是:
1.获得原文件名并拼接上UUID
2.通过transferTo方法将文件保存到指定的目录下,并使用新创的文件名
中间可以穿插判断文件后缀是否合法等操作。
后端input_file方法代码:
java
if (uploadFile.isEmpty()) {
return "未接收到文件";
}
String savePath = 改成自己的文件夹路径;
File dir = new File(savePath);
if (!dir.exists()) {
dir.mkdirs();
}
// 1. 获取原始文件名,只用来截取后缀,不再参与路径拼接
String originalName = uploadFile.getOriginalFilename();
String suffix = originalName.substring(originalName.lastIndexOf("."));
// 2. UUID生成全新唯一文件名,杜绝重名+路径穿越
String storeFileName = UUID.randomUUID().toString().replace("-", "") + suffix;
// 3. 安全拼接路径,存储名完全由后端控制,不受前端文件名影响
File target = new File(savePath + storeFileName);
try {
uploadFile.transferTo(target);
// 数据库可以保存 storeFileName + originalName
return "success upload,存储文件名:" + storeFileName;
} catch (IOException e) {
e.printStackTrace();
return "lose upload";
}
快速实验(直接使用原来的文件名保存,无法防止文件重名):
后端input_file方法代码:
java
@ResponseBody
@RequestMapping("/input_file")
public String input_file(@RequestParam("uploadFile") MultipartFile uploadFile) {
if (uploadFile.isEmpty()) {
return "未接收到文件";
}
// 保存文件到本地,可自行修改路径
String savePath = 改成自己的文件夹路径;
File dir = new File(savePath);
if (!dir.exists()) {
dir.mkdirs();
}
File target = new File(savePath + uploadFile.getOriginalFilename());
try {
uploadFile.transferTo(target);
return "success upload";
} catch (IOException e) {
e.printStackTrace();
return "lose upload";
}
}
前端完整代码(jquery需要自己引入):
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../js/jquery.js"></script>
</head>
<body>
<div>文件上传实验</div>
<input class="file-value" type="file"><br><br>
<input class="file-input-button" type="button" value="上传">
<script>
$(".file-input-button").on("click",function(){
let file = $(".file-value")[0].files[0];
if(!file) return alert("请选择文件");
let fd = new FormData();
fd.append("uploadFile", file);
$.ajax({
url:"/input_file",
type:"POST",
contentType:false,
processData:false,
data: fd,
success:function(res){
alert("上传成功:" + res);
},
error:function(){
alert("上传失败");
}
})
})
</script>
</body>
</html>
或者前端可以使用form表单格式提交也是一样的,但是必须加上 enctype="multipart/form-data",method=post
html
<form action="/input_file" method="post" enctype="multipart/form-data">
文件:<input type="file" name="uploadFile">
<button type="submit">上传</button>
</form>