文件上传和下载
一、文件的上传
1.代码
<%--
Created by IntelliJ IDEA.
User: 韩顺平
Version: 1.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 指定了base标签 -->
<base href="<%=request.getContextPath()+"/"%>">
<style type="text/css">
input[type="submit"] {
outline: none;
border-radius: 5px;
cursor: pointer;
background-color: #31B0D5;
border: none;
width: 70px;
height: 35px;
font-size: 20px;
}
img {
border-radius: 50%;
}
form {
position: relative;
width: 200px;
height: 200px;
}
input[type="file"] {
position: absolute;
left: 0;
top: 0;
height: 200px;
opacity: 0;
cursor: pointer;
}
</style>
<script type="text/javascript">
function prev(event) {
//获取展示图片的区域
var img = document.getElementById("prevView");
//获取文件对象
var file = event.files[0];
//获取文件阅读器: Js的一个类,直接使用即可
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {
//给img的src设置图片url
img.setAttribute("src", this.result);
}
}
</script>
</head>
<body>
<!-- 表单的enctype属性要设置为multipart/form-data
enctype="multipart/form-data" 表示提交的数据是多个部分构造,有文件和文本
-->
<form action="upload" method="post" enctype="multipart/form-data">
家居图: <img src="2.jpg" alt="" width="200" height="200" id="prevView">
<%-- 小伙伴愿意完成自己测试--%>
<input type="file" name="pic" id="" value="" onchange="prev(this)"/>
<%-- <input type="file" name="pic" id="" value="提交文件" onchange="prev(this)">--%>
家居名: <input type="text" name="name"><br/>
<input type="submit" value="上传"/>
</form>
</body>
</html>
```txt
package com.leon.servlet;
import com.leon.util.UploadTool;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
/**
* ClassName:Servlet
* Package:com.leon.servlet
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2023/11/19 22:31
* @Version: 1.0
*/
public class FileUploadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// System.out.println("FileUploadServlet 被调用了~~~~");
// System.out.println(request.getContentType());
// UploadTool.upload(request,response);
/*
1. 通过ServletFileUpload类中的静态方法isMultipartContent,
来判断Content-Type是否是multipart/form-data,也可以理解为表单中的
enctype属性是否等于multipart/form-data
2.底层是这样判断的: contentType.toLowerCase().startsWith("multipart/");
返回的是boolean值
*/
if (ServletFileUpload.isMultipartContent(request)) {
/*
1.因为ServletFileUpload类的构造器是public ServletFileUpload(FileItemFactory fileItemFactory){}
需要一个FileItemFactory接口,而DiskFileItemFactory实现了FileItemFactory接口,所以需要DiskFileItemFactory
用于构建一个解析上传数据的工具,也就是ServletFileUpload类
*/
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
// 创建一个解析上传数据的工具
// 也可以直接与上面的进行合并:new ServletFileUpload(new DiskFileItemFactory());
ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
// 设置编码
servletFileUpload.setHeaderEncoding("utf-8");
try {
/*
1.这个servletFileUpload.parseRequest(request)方法是把
表单提交的数据封装到FIleItem中
2.这个List使用的泛型对象是 FileItem,为什么使用这个对象呢?
3.因为底层返回的这个List中存储的对象就是FileItem
源码:
List items = new ArrayList();
FileItem fileItem;
for(; iter.hasNext(); items.add(fileItem)) {....}
*/
List<FileItem> list = servletFileUpload.parseRequest(request);
// 如果不了解对象是什么结构有这几个方法[1.输出该对象2.debug]
// 这样底层的东西就会展现出来
// 循环遍历
/*
name表示你的文件名
name=3.png,
StoreLocation 表示上传的文件临时存放的地方,包括临时的文件名
StoreLocation=C:\software\javaTool\tomcat\apache-tomcat-9.0.82\temp\ upload_46ad27a5_18bec217fa1__7fd7_00000002.tmp,
size表示文件的大小
size=76442bytes,
isFormField表示是否是文本
isFormField=false,
FieldName表示该表单的name属性值 <input type="file" name="pic" id="" value="" onchange="prev(this)"/>
FieldName=pic
-------------------------------------------------------
name表示文件名,因为是一个文本,不是一个文件,没有文件名所以等于null
name=null,
// StoreLocation表示该数据临时存放的地方包括临时的文件名
StoreLocation=C:\software\javaTool\tomcat\apache-tomcat-9.0.82\temp\ upload_46ad27a5_18bec217fa1__7fd7_00000003.tmp,
size表示数据的大小
size=6bytes,
isFormField是否是文本
isFormField=true,
FieldName表示该表单的name属性值 <input type="text" name="name">
FieldName=name
*/
for (FileItem fileItem : list) {
// System.out.println(fileItem);
// 进行处理吗,判断是否是一个文件
if (fileItem.isFormField()) {
/*
不是文件,获取它的input标签的value值
getName()因为这个是获取name的,也就是获取文件名,因为它没有文件名,所以值为空
String name = fileItem.getName();
System.out.println(name);
getString()这个是获取input标签的value值,但是还需要设置编码格式
*/
String fieldName = fileItem.getString("utf-8");
System.out.println(fieldName);
} else {
// 是文件,获取文件名
String name = fileItem.getName();
// System.out.println("文件名:" +name);
/*
1.创建一个目录,这个目录在实际的工程目录下,存放上传的文件
2.创建一个目录名
*/
String filePath = "/upload/";
/*
1.获取一个完整的路径
2.getRealPath("/")获取真实的工作路径
3.getRealPath(filePath)获取真实的工作路径然后把filePath拼接在真实的工作路径后面
*/
String filRealPath = request.getServletContext().getRealPath(filePath);
// System.out.println(filRealPath);
// 创建该目录
File fileRealPathDirectory = new File(filRealPath+ UploadTool.getDateDirectory());
/*
System.out.println(fileRealPathDirectory);
1.直接输出这个File类是一个路径
2.因为File类的toString()方法是输出该文件的路径
*/
// 判断该目录是否存在,如果不存在则创建
if(!fileRealPathDirectory.exists()){
// 判断是否创建成功,mkdirs表示创建多级目录
if(fileRealPathDirectory.mkdirs()){
System.out.println("创建成功");
}else {
System.out.println("创建失败");
}
}
// 将文件拷贝到创建的目录中
/*
1.为了防止文件名相同而导致的覆盖,使用UUID来解决,随机生成一个UUID作为文件名的一部分
为了防止加了UUID也重名再加上一个时间戳
2.用"_"间隔开来方便日后要获取该文件的真正文件名时,更方便
*/
name = UUID.randomUUID() +"_" + System.currentTimeMillis() + "_" + name ;
String fileFullPath = fileRealPathDirectory +"/"+ name;
fileItem.write(new File(fileFullPath));
// 响应上传成功
// 设置Context-Type
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("上传成功~~~");
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
System.out.println("不是~~~");
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
2.注意事项
如果将文件都上传到一个目录下,当上传文件很多时,会造成访问文件速度变慢,因此可以将文件上传到不同目录比如一天上传的文件,统一放到一个文件夹
一个完美的文件上传,要考虑的因素很多,比如断点续传、控制图片的大小,尺寸,分片上传,防止恶意上传等,在项目中,可以考虑使用WebUploader组件(百度开发的)
http://fex.baidu.com/webuploader/doc/index.html
文件上传功能,在项目中建议有限制的使用,一般用头像、证明、合同、产品展示等,如果不加限制,会造成服务器空间被大量占用[比如b站评论,就不能传图片,微信发一次朋友圈最多9张图片等….]
如果你在编写程序时,在web目录下创建一个目录时,如果目录中没有文件,在Tomcat启动时,不会在实际的工作目录下创建你在web目录下创建的目录,因为Tomcat不会在实际的工作目录中创建空目录,所以在你要创建的目录下随意的添加一个文件即可,然后再build一下后再重启或重新发布
二、文件下载
1.代码
<%--
Created by IntelliJ IDEA.
User: 韩顺平
Version: 1.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件下载</title>
<base href="<%=request.getContextPath()+"/"%>">
</head>
<body>
<h1>文件下载</h1>
<a href="download?name=1.png">点击下载1图片</a><br/><br/>
<a href="download?name=2.png">点击下载2图片</a><br/><br/>
<a href="download?name=韩顺平java基础.pdf">韩顺平java基础.pdf</a><br/><br/>
</body>
</html>
```txt
package com.leon.servlet;
import org.apache.commons.io.IOUtils;
import sun.misc.BASE64Encoder;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
/**
* ClassName:Servlet
* Package:com.leon.servlet
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2023/11/21 20:49
* @Version: 1.0
*/
public class FileDownLoadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// System.out.println("FileDownLoadServlet 被调用了");
// 设置请求编码
request.setCharacterEncoding("utf-8");
// 获取要下载的文件名
String fileName = request.getParameter("name");
// System.out.println(name);
/*
1. 通过ServletContent类来获取文件的MIME类型
2. 通过获取到的MIME类型然后再设置Content-Type的类型
*/
ServletContext servletContext = request.getServletContext();
// 创建一个目录,也就是你存放文件的那个目录
String downLoadPath = "/download/";
// 创建一个完整的目录,就是将获取到的文件名与你创建的存放目录进行拼接
String downLoadFileFullPath = downLoadPath + fileName;
// 通过你给出的路径,然后去找该文件,再获取文件的MIME类型,这个是从web工程根目录开始计算的
String mimeType = servletContext.getMimeType(downLoadFileFullPath);
// System.out.println(mimeType);
// 设置Content-Type
response.setContentType(mimeType);
/*
1.给你http响应,设置响应头 Content-Disposition
2.这里有很多细节,比如不同的浏览器写法不一样,要考虑编码
3.火狐 是文件名中文需要base64编码,而ie/chrome是URL编码
4.Content-Disposition 是指定下载的数据的展示形式,如果attachment 则使用文件下载方式
*/
if(request.getHeader("User-Agent").contains("Firefox")){
// Base64编码
response.setHeader("Content-Disposition","attachment;filename==?UTF-8?B?"+
new BASE64Encoder().encode(fileName.getBytes("UTF-8"))+"?=");
}else {
response.setHeader("Content-Disposition","attachment;filename="+ URLEncoder.encode(fileName,"UTF-8"));
}
// 通过ServletContext,创建一个字节输入流
InputStream resourceAsStream = servletContext.getResourceAsStream(downLoadFileFullPath);
// 创建一个字节输出流
ServletOutputStream outputStream = response.getOutputStream();
// 使用工具将输入流的数据拷贝到输出流中
IOUtils.copy(resourceAsStream,outputStream);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
}
2.注意事项
- 文件下载中,文件名中文处理,在不同的浏览器需要不同的编码方式,如火狐需要base64,而ie/chrom需要URL
- 对于网站的文件,很多文件使用另存为即可下载,对于大文件(文档、视频),会使用专业的下载工具(迅雷,百度,腾讯,华为网盘等)
- 对于不同的浏览器,在把文件下载完毕后,处理的方式不一样,有些是直接打开文件,有些是直接将文件下载到本地/下载目录