1 文件上传
1.1 文件上传原理分析
1.1.1 文件上传的前提
- 提供form表单,method必须是post
- form表单的enctype,必须是
multipart/form-data
- 提供
input type="file"
类的上传输入域
1.1.2 enctype属性
**enctype作用:**告知服务器请求正文MIME类型。(请求消息头:Context-Type作用是一致的)
可选值:
application/x-www-form-urlencoded
(默认):
正文:username=admin&password=123
服务器获取数据:String username = request.getParameter(“username”);
multipart/form-data
:
正文:
服务器获取数据:request.getParameter(String)方法获取指定的表单字段字符内容。但是文件上传表单已经不在是字符内容,而是字节内容,所以无效,所以利用字节流读取数据。request.getInputStream();
1.2 文件上传案例
1.2.1 upload.jsp
<form action="${pageContext.request.contextPath}/UploadServlet" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
文件:<input type="file" name="file"><br>
<input type="submit" value="提交">
</form>
1.2.2 UploadServlet
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.获取表单提交的原始数据
InputStream is =request.getInputStream();
//2.读取数据
byte[] buf = new byte[1024];
int len=0;
while((len = is.read(buf))!=-1){
//把字节转成utf-8的编码字符串
String str = new String(buf,0,len,"utf-8");
System.out.println(str);
}
}
}
由于需要将读取到的字节进行分割成有效数据,所以利用第三方框架来进行处理。
1.3 家用第三方解析文件
1.3.1 fileupload概述
- fileupload是由apache的commons组件提供的上传组件。它最主要的工作就是帮我们解析request.getInputStream()。
- 使用fileupload只需要2个jar包
commons-fileupload.jar,核心包;
commons-io.jar,依赖包。
在idea中,导入的包不会立即起效,需要对导入的包进行应该是加载一类。
选中java之后,选中java->本项目->web->WEB-INF->lib点击ok即可。
1.3.2 fileupload的核心类:
DiskFileItemFactory、ServletFileUpload、FileItem
1.3.3 解析原理
通过浏览器上传的数据中,每一个表单项input
都封装成了一个FileItem对象,利用DiskFileItemFactory工厂对象构造一个FileItem集合类ServletFileUpload,来存储FilItem对象。
1.3.4 FileItem对象介绍
- FileItem对象对应一个表单项(表单字段)。可以是文件字段或普通字段
- boolean isFormField():判断当前表单字段是否为普通文本字段,如果返回false,说明是文件字段;
- String getFieldName():获取字段名称,例如:,返回的是username;
- String getString():获取字段的内容,如果是文件字段,那么获取的是文件内容,当然上传的文件必须是文本文件;
- String getName():获取文件字段的文件名称;(a.txt)
- String getContentType():获取上传的文件的MIME类型,例如:text/plain。
- int getSize():获取上传文件的大小;
- InputStream getInputStream():获取上传文件对应的输入流;
- void write(File):把上传的文件保存到指定文件中。
- delete():删除临时文件
1.3.5 使用fileupload实现文件上传代码
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//解析表单数据
//1.创建工厂类
DiskFileItemFactory factory = new DiskFileItemFactory();
//2.获取文件上传对象
ServletFileUpload sfu = new ServletFileUpload(factory);
//设置单个文件上传大小
sfu.setFileSizeMax(1024*1024*20);//文件最大为2M
//3.获取FileItem
try {
//FileItem相当于一个input标签的数据
List<FileItem> items = sfu.parseRequest(request);
for (FileItem item:items) {
if(item.isFormField()){//普通数据
System.out.println(item.getFieldName() + "---" + item.getString("utf-8"));
}else {//文件数据
System.out.println("文件名:"+ item.getName());
System.out.println("文件类型:" + item.getContentType());
System.out.println("文件大小:" + item.getSize());
System.out.println("文件内容.." +item.getString());
//如果是文件,需要把文件保存到本地
//1.指定文件保存的目录
//添加一个日期目录
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String dateStr = sdf.format(new Date());
//保存为日期文件
//String path = request.getServletContext().getRealPath("upload/"+dateStr);
//把日期转成16进制的字节串
String dateHexStr = Integer.toHexString(dateStr.hashCode());
String path = request.getServletContext().getRealPath("upload/"+dateHexStr);
System.out.println("保存文件的路径:"+path);
//2.创建文件夹
File dir = new File(path);
if(!dir.exists()){
dir.mkdirs();
System.out.println("创建文件夹了");
}else{
System.out.println("文件夹已经存在了");
}
//3.把上传的文件保存
//3.1 指定文件保存的名字
//后缀名
String[] arr = item.getName().split("\\.");
String suffix = arr[arr.length-1];
//文件名
String fileName = UUID.randomUUID().toString()+"."+suffix;
//3.2 拼接文件保存路径
String fileSavePath = path + "\\" + fileName;
System.out.println("保存文件完整路径:"+fileSavePath);
//3.3 保存
item.write(new File(fileSavePath));
item.delete();//删除临时文件
}
}
}catch(Exception e){
e.printStackTrace();
}
}
其中的注意事项如下:
①文件上传保存路径的两种方案
- 方案一:添加日期目录,方便分类查找
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String dateStr = sdf.format(new Date());
//保存为日期文件
String path = request.getServletContext().getRealPath("upload/"+dateStr);
- 方案二:将日期20180808转换成16进制字符串
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String dateStr = sdf.format(new Date());
//把日期转成16进制的字节串
String dateHexStr = Integer.toHexString(dateStr.hashCode());
String path = request.getServletContext().getRealPath("upload/"+dateHexStr);
②文件上传注意的问题
- 中文乱码问题
- 表单普通字段乱码用item.getString(“utf-8”);
- 文件名乱码用SerlvetFileUpload.setHeaderEncoding(“utf-8”);
- 文件上传大小限制问题
- 单个文件大小:
ServletFileUpload.setFileSizeMax(字节) - 总文件大小:
ServletFileUpload.setSizeMax(字节)
- 单个文件大小:
- 临时文件
DiskFileItemFactoru:
作用:参数FileItem对象
内部有一个缓存,缓存大小默认是10Kb。如果上传的文件超过10Kb,用磁盘作为缓存。存放缓存文件的目录在哪里?默认是系统的临时目录。
如果自己用IO流实现的文件上传,要在流关闭后,清理临时文件。调用FileItem.delete();
1.4 多文件上传
字节设置超过两个及两个的文件上传:
动态添加文件上传
HTML
<form action="${pageContext.request.contextPath}/UploadServlet" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<div id="filesDiv">
<div>文件:<input type="file" name="file"><input type="button" value="删除" onclick="deleteDiv(this)"></div>
</div>
<input type="button" value="添加" onclick="addFileUploadDiv()">
<input type="submit" value="提交">
</form>
JS
<script>
function addFileUploadDiv() {
//1.通过id拿到filesDiv
var div = document.getElementById("filesDiv");
div.innerHTML += '<div>文件:<input type="file" name="file"><input type="button" value="删除" οnclick="deleteDiv(this)"></div>'
}
function deleteDiv(input) {
//通过父标签移除子标签
var div = document.getElementById('filesDiv');
div.removeChild(input.parentNode);
}
</script>
2 文件下载
2.1 文件下载需要设置响应头
- Content-Disposition
attachment;filename=xx.xx | 以附件形式下载 |
- Content-Type
application/octet-stream | 二进制流 |
text/html | 网页 |
text/json | 返回的是一个json格式的字符串 |
2.2 案例下载代码
/**
* 文件下载的注意事项
* 1.设置两个响应头
* 2.如果文件是中文名,要进行URLEncode
* 3.通过输入流读取数据,通过输出流response.getOutputStream输出数据给客户端
*
* 练习:
* 通过参数来下载指定文件
*/
@WebServlet("/DownloadServlet")
public class DownloadServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//实现文件的下载
//1.指定文件的下载路径
String fileName = "text.txt";
String path = "C:\\Users\\Yyyz\\Desktop\\test\\"+fileName;
//2.设置响应头
//如果是中文文件,要设置URL编码
fileName = URLEncoder.encode(fileName,"utf-8");
response.setHeader("Content-Disposition","attachment;filename="+fileName);//以附件的形式下载
response.setContentType("application/octet-stream");//以二进制的数据返回给客户端
//3.通过输入输出流将文件下载到客户端
//通过输入流读数据
FileInputStream fis = new FileInputStream(new File(path));
//通过输出流写数据
ServletOutputStream sos = response.getOutputStream();
byte[] buf = new byte[1024];
int len=0;
while((len=fis.read(buf))!=-1){
sos.write(buf,0,len);
}
//关闭流
fis.close();
sos.close();//输出流可以不用关,tomcat会自动关闭。
}
}
补充:该部分内容来自于java3y(微信公众号)的文件上传和下载
FileUpload的开发步骤:
- 创建解析器工厂对象【DiskFileItemFactory】
- 通过解析器工厂创建解析器【ServletFileUpload】
- 调用解析器方法解析request对象,得到所有上传的内容【list】
- 遍历list,判断每个对象是否是上传文件
- 如果是普通表单字段,得到字段名和字段值
- 如果是上传文件,调用InputSteam方法得到输入流,读取上传的数据
SmartUpload
需要导入smartupload.jar
(由于没有找到完整的jar包,简单了解。)中文乱码时可能没有好的解决方案
文件上传的细节
- 上传文件的大小以及临时文件需要处理
- 上传文件的位置不能再web服务器管理之下,不然容易出安全问题
- 文件名最好独一无二
- 用户量大了之后,文件会非常巨大,应该将上传的文件分布到不同的目录之下