文件的上传和下载
文件的上传和下载是web系统中常见的功能,用于在客户端和浏览器之间传输文件,允许用户方便地共享文件、备份数据、更新软件等。
文件的上传
页面demo
<form action="/uploadFile" method="post" enctype="multipart/form-data">
<img src="" alt="暂无数据!(这里是预览图片)" id="previewImage" width="180px" height="180px">
<input type="file" name="uploadFile" id="uploadFile" value="上传图片" />
<input type="submit" value="上传" />
</form>
<script>
// 图片预览 绑定上传元素change事件
document.getElementById('uploadFile').addEventListener('change', function(event) {
let file = event.target.files[0] // 获取选中的文件
if (file) {
let reader = new FileReader() // 创建FileReader对象
reader.readAsDataURL(file)
reader.onload = function (){
document.getElementById('previewImage').setAttribute('src',this.result) // 设置图片的URL并展示在img元素中
}
}
})
</script>
后台Servlet处理
- 使用servlet的getPart() api来获取文件数据(注:必须在servlet3.0和tomcat8使用)
@WebServlet("/uploadFile")
@MultipartConfig //支持part提交的方式
public class MyServlet2 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置请求中的字符编码
req.setCharacterEncoding("UTF-8");
// 获取上传的文件
Part uploadFile = req.getPart("uploadFile");
String fname = uploadFile.getSubmittedFileName();
System.out.println("上传的文件名: "+fname);
//获取存储路径
String realPath = req.getServletContext().getRealPath("/");
//写入数据
uploadFile.write(realPath+"/"+fname);
}
}
在上面的代码中可以考虑加上一些限制因素
@MultipartConfig(fileSizeThreshold = 1024 * 1024 * 2, // 2MB(设置文件大小,超过这个大小,文件将会写入磁盘而不是内存,防止内存溢出)
maxFileSize = 1024 * 1024 * 10, // 10MB(允许上传的单个文件的最大大小,超过会拒绝上传)
maxRequestSize = 1024 * 1024 * 50) // 50MB(指定整个请求的最大大小,包括所有文件和表单数据。超过会拒绝上传)
- 使用commons-fileupload库来上传文件
先添加库(或依赖)
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId> <!-- 文件上传 -->
<version>1.4</version>
</dependency>
主要涉及的api有:
DiskFileItemFactory: 用于创建 FileItem 对象的工厂类。FileItem 对象表示上传的文件或表单字段。
ServletFileUpload: 用于解析 HTTP 请求,将请求中的文件项(FileItem)解析为文件或表单字段。它可以处理多部分请求,即包含文件上传的表单。
@WebServlet("/uploadFile")
public class MyServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 判断是否为文件表单(enctype="multipart/form-data")
if (ServletFileUpload.isMultipartContent(req)) {
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
servletFileUpload.setHeaderEncoding("utf-8"); //解决接受到的中文文件名为乱码的问题
try {
// 获取完整路径
String fileRealPath = this.getServletContext().getRealPath("/upload/");
System.out.println("获取到的完整路径:"+fileRealPath);
File fileUpload = new File(fileRealPath);
if (!fileUpload.exists()) { //如果文件路径不存在就创建
System.out.println(fileUpload.mkdirs()?"创建文件成功":"文件创建失败!");
}
//获取请求里面的数据
List<FileItem> fileItems = servletFileUpload.parseRequest(req);
for (FileItem fileItem : fileItems) {
System.out.println( fileItem);
//判断form表单中的数据类型
if (fileItem.isFormField()){ //判断是文件还是文本数据(如果为true则是text)
String name = fileItem.getString("utf-8");
System.out.println("name =" +name);
}
// 获取文件名(注意:不同浏览器提交的文件名方式不一样,有的是 D:/test/t1.jpg 有点是t1.jpg)
String fname = fileItem.getName();
fname = fname.substring(fname.lastIndexOf("\\")+1); //只截取文件名
fileItem.write(new File(fileRealPath + fname));
//删除临时文件
fileItem.delete();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
后台Spring处理
@PostMapping("/fileUpload")
@ResponseBody
public String fileUpload(@RequestParam("uploadFile") MultipartFile file, HttpServletRequest request) {
if (!file.isEmpty()) {
try {
String fileName = file.getOriginalFilename();
// 使用ServletContext获取当前Web应用的根目录
String savePath = request.getServletContext().getRealPath("/") +"/uploads/"+ fileName;
file.transferTo(new File(savePath)); //写入指定文件
return "yes";
} catch (IOException e) {
return "error";
}
} else {
return "file Empty!";
}
}
文件上传注意事项:
- 在保存使用文件名时,可以加一些前缀(比如uuid、timestamp)+文件名,防止文件同名在保存时被覆盖。
- 如果文件都上传到同一个目录下,当上传文件很多时会导致访问文件变慢,因此可以将文件放在不同目录下,比如一天上传的文件都放在一个文件夹下。
- 一个完美的文件上传要考虑的因素有很多,比如断点续传、文件大小、尺寸大小、分片上传、防止恶意上传等,在项目部中可以考虑使用WebUploader组件
- 在项目中需要考虑有限制的使用文件上传,如果上传太多文件会造成服务器空间被大量占用(比如微信朋友圈一次最多发9张图片等)
文件下载
<img src="upload/test.jpg" alt="暂无数据!" width="180px" height="180px">
<button onclick="location.href='/downloadFile'">下载图片到本地</button>
文件下载时,请求的响应头有:
- Content-Disposition:表示下载的数据展示方式,比如是内联形式(网页形式或者网页一部分),或者是文件下载方式 attachment
- Content-Type:指定返回数据的类型(MIME类型)
@WebServlet("/downloadFile")
public class MyServlet2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置请求中的字符编码
req.setCharacterEncoding("UTF-8");
// 要下载的文件路径
String downPath = "/upload/test.jpg";
//设置请求响应头Content-Disposition Content-Type
String mimeType = req.getServletContext().getMimeType(downPath);
resp.setContentType(mimeType);
//设置Content-Disposition
// 针对不同浏览器,对下载时,显示文件名进行编码u川和base64
if(req.getHeader("User-Agent").contains("Firefox")){
//火狐Base64编码
resp.setHeader("Content-Disposition","attachment;filename==?UTF-8?B?"+
new BASE64Encoder().encode("test.jpg".getBytes(StandardCharsets.UTF_8))+"?=");
}else{
//其他主流ie/chrome 使用URL编码操作
resp.setHeader("Content-Disposition","attachment;filename="+
URLEncoder.encode("test.jpg","UTF-8"));
}
InputStream resourceAsStream = req.getServletContext().getResourceAsStream(downPath);
//获取响应的输出流(Servlet有个Writer写入返回数据(文本),如果是二进制数据则使用OutputStream)
OutputStream outputStream = resp.getOutputStream();
//将输入流的数据写入输出流
byte[] buffer = new byte[1024];
int length;
while ((length = resourceAsStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
}
}
注意设置响应头的Content-Disposition(因为不同的浏览器有不同的编码方式)
//设置Content-Disposition
// 针对不同浏览器,对下载时,显示文件名进行编码u川和base64
if(req.getHeader("User-Agent").contains("Firefox")){
//火狐Base64编码
resp.setHeader("Content-Disposition","attachment;filename==?UTF-8?B?"+
new BASE64Encoder().encode("test.jpg".getBytes(StandardCharsets.UTF_8))+"?=");
}else{
//其他主流ie/chrome 使用URL编码操作
resp.setHeader("Content-Disposition","attachment;filename="+
URLEncoder.encode("test.jpg","UTF-8"));
}