一、文件的上传和下载
1、文件上传介绍
1.1什么是文件上传:
要将客户端(浏览器)大数据存储到服务器端,不将数据直接存储到数据库中,而是要将数据存储到服务器所在的磁盘上,这就要使用文件上传。
1.2为什么使用文件上传
通过文件上传,可以将浏览器端的大数据直接保存到服务器端。不将数据保存到数据库中,而是保存到服务器磁盘上,这样减少了数据库服务器的压力,对数据的操作更加灵活
2、文件上传的原理分析
2.1概念:
所谓的文件上传就是服务器端通过request对象获取输入流,将浏览器端上传的数据读取出来,保存到服务器端
2.2文件上传的必要前提:
a、提供form表单,method必须是post
b、form表单的enctype必须是multipart/form-data
c、提供input type="file"类的上传输入域
2.3enctype属性
作用:告知服务器请求正文的MIME类型。(请求消息头:Content-Type作用是一致的)
可选值:
l application/x-www-form-urlencoded(默认):
正文:name=admin&password=123
服务器获取数据:String name = request.getParameter("name");
l multipart/form-data:
正文:
服务器获取数据:request.getParameter(String)方法获取指定的表单字段字符内容,但文件上传表单已经不在是字符内容,而是字节内容,所以失效。
文件上传:解析请求正文的每部分的内容。
2.3enctype属性
3、借助第三方的上传组件实现文件上传
3.1fileupload概述
fileupload是由apache的commons组件提供的上传组件。它最主要的工作就是帮我们解析request.getInputStream()。
导入commons-fileupload相关jar包
l commons-fileupload.jar,核心包;
l commons-io.jar,依赖包。
3.2 fileupload的核心类有:
DiskFileItemFactory、ServletFileUpload、FileItem。
a、解析原理
3.3fileupload简单应用
使用fileupload组件的步骤如下:
1. 创建工厂类DiskFileItemFactory对象:
DiskFileItemFactory factory = new DiskFileItemFactory()
2. 使用工厂创建解析器对象:
ServletFileUpload fileUpload = new ServletFileUpload(factory)
3. 使用解析器来解析request对象:
List<FileItem> list = fileUpload.parseRequest(request)
FileItem对象对应一个表单项(表单字段)。可以是文件字段或普通字段
l boolean isFormField():判断当前表单字段是否为普通文本字段,如果返回false,说明是文件字段;
l String getFieldName():获取字段名称,例如:<input type=”text” name=”username”/>,返回的是username;
l String getString():获取字段的内容,如果是文件字段,那么获取的是文件内容,当然上传的文件必须是文本文件;
l String getName():获取文件字段的文件名称;(a.txt)
l String getContentType():获取上传的文件的MIME类型,例如:text/plain。
l int getSize():获取上传文件的大小;
l InputStream getInputStream():获取上传文件对应的输入流;
l void write(File):把上传的文件保存到指定文件中。
l delete();
4、文件上传时要考虑的几个问题(经验分享)
a、保证服务器的安全
把保存上传文件的目录放在用户直接访问不到的地方。
b、避免文件被覆盖
让文件名唯一即可
c、避免同一个文件夹中的文件过多
方案一:按照日期进行打散存储目录
方案二:用文件名的hashCode计算打散的存储目录:二级目录
d、限制文件的大小:web方式不适合上传大的文件
单个文件大小:
ServletFileUpload.setFileSizeMax(字节)
总文件大小:(多文件上传)
ServletFileUpload.setSizeMax(字节)
e、上传字段用户没有上传的问题
通过判断文件名是否为空即可
f、临时文件的问题
DiskFileItemFactory:
作用:产生FileItem对象
内部有一个缓存,缓存大小默认是10Kb。如果上传的文件超过10Kb,用磁盘作为缓存。
存放缓存文件的目录在哪里?默认是系统的临时目录。
如果自己用IO流实现的文件上传,要在流关闭后,清理临时文件。
FileItem.delete();
5、入门案例
5.1DiskFileItemFactory
1.设置缓存大小
factory.setSizeThreshold(1024*1024); //设 置为1m 默认是10k
2.设置临时文件存储位置
File temp=new File(this.getServletContext().getRealPath("/temp"));
factory.setRepository(temp); //可以指定 临时文件存储位置,默认是系统的临时文件 存储位置
5.2ServletFileUpload
1.parseRequest方法
List<FileItem> pareRequest(HttpServletRequest request)
得到所有的上传信息,将每一部分映射成FileItem对象
2.isMultipartContent方法
boolean isMultipartContent(HttpServletRequest request)
这个方法返回值是boolean,它是用于判断当前表单是否是一个上传的表单,简单说,就判断它的encType的值是否是 multipart/form-data.
3.setHeaderEncoding方法
用于解决上传文件名称中文乱码问题
4.设置上传文件大小
void setFileSizeMax(long fileSizeMax) 设置单个文件上传大小
void setSizeMax(long sizeMax) 设置总文件上传大小
5.3FileItem
1.isFormField方法
这个方法返回的是boolean类型,它是判断当前组件是否是上传组件 简单说,就是 判断type="file",如果返回true,代表不是上传组件,返回false,代表是上传组件
2.getName方法
获取非上传组件的上传文件的名称,如果是非上传组件,返回的是null
3.getFieldName方法
获取组件名称,简单说,就是表单的元素的name值。
4.getString方法
获取非上传组件的value值
getString()有一个重载的方法 getString(String encoding)可以解决乱码问题
5.getInputStream方法
通过FileItem.getInputStream();可以获取一个输入流,这个输入流就可以
读取出上传文件内容。
6.delete方法
它是用于上传完成后,删除临时文件的
6、多文件上传
function addFile() {
//1.得到id叫content的div
var div=document.getElementById("content");
//2.向其中添加一段html代码
div.innerHTML+="<div><input type='file' name='f'><input type='button' value='remove File' οnclick='removeFile(this);'></div>";
}
function removeFile(btn){
document.getElementById("content").removeChild(btn.parentNode);
}
7、文件上传问题
7.1 文件重命名
1.上传操作文件重名问题分析
每一个客户端都可以进行文件上传操作,那么当我们上传的文件过多,一定会出现同名的文件,那么在服务器端只能保存一个,对于这个问题,我们在上传文件时,就需要考虑文件重名问题
2.上传操作文件重名解决方案
一般情况下,对于上传文件,为了保证不重名,会给文件起一个随机名.
a.一种方案是使用uuid.
b.一种方案是使用毫秒值
7.2 存储位置
1.上传操作文件存储位置分析
本质就是上传的文件是否允许浏览器端直接访问。例如:商品添加时需要一个图片,这个图片一定是可以直接被浏览器端访问的。
2.上传操作文件存储位置解决方案
a.允许被浏览器端访问:放置在WebRoot下,但不能是WEB-INF或META-INF下其及子目录下
b.不允许被浏览器端访问:
工程下:放置在WEB-INF或META-INF及其子目录下.
不放在工程下
7.3 目录分离
1.上传操作文件过多问题分析
当我们上传文件过多,并且保存在同一个目录下时,我们就需要考虑怎样处理它们,因为一个目录下文件过多,不仅降低性能,操作时也不方便。
2.上传操作文件过多解决方案
为了防止同一个目录下方上传文件数量过多
1) 按照上传时间进行目录分离 (周、月 )
2) 按照上传用户进行目录分离 ----- 为每个用户建立单独目录
3) 按照固定数量进行目录分离 ------ 假设每个目录只能存放3000个文件 ,每当一个目录存满3000个文件后,创建一个新的目录
4).按照唯一文件名的hashcode 进行目录分离
8、文件的下载
8.1 什么是下载
所谓的下载,其实就是将服务器端的资源通过io流写回到浏览器端。
8.2 超链接实现下载
如果文件可以直接被浏览器解析,会直接在浏览器上打开。
如果不能解析,可以下载
通过另存为进行下载
这种下载方式:当路径提交时,会通过缺省的servlet将文件直接写回到浏览器端.
8.3 超链接下载问题分析
不能被浏览器直接访问的文件,不可以使用超连接下载.
原因是在http响应头中content-type,如果它的值可以被浏览器解析,
那么响应回的内容就会被浏览器端直接解析,如果content-type的值,不可以被浏览器直接解析,那么就会下载。
8.4 服务器端编码实现下载
1.服务器端编码下载原理分析
通过response可以获取输出流,我们将要下载的资源,通过response获取的输出流直接写回到浏览器端就可以。
2.服务器端下载两个响应头设置
a.怎样能通知浏览器,下载文件是什么?
通过response.setContentType()正设置响应数据的 mimeType类型.
要想获取一个文件的mimeType类型
ServletContext.getMimeType(String filename);
b.下载时可不可以有个下载的提示框?
response.setHeader("Content- Disposition","attachment;filename=下载文件名");
8.5 关于下载时乱码问题分析与解决
对于下载时,我们在显示下载文件名称时,如果包含了中文,就可能出现乱码问题,出现的原因,是对于不同的浏览器,它们在处理下载文件时的编码不一致,ie浏览器使用的是utf-8编码,而firefox浏览器使用的是base64编码
if (agent.contains("MSIE")) {
// IE浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
} else if (agent.contains("Firefox")) {
// 火狐浏览器
BASE64Encoder base64Encoder = new BASE64Encoder();
filename = "=?utf-8?B?"
+ base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
} else {
// 其它浏览器
filename = URLEncoder.encode(filename, "utf-8");
}
简单处理下载乱码问题
其实我们也可以使用下面代码解决下载时乱码问题
response.setHeader( "Content-Disposition", "attachment;filename=" + new String( fileName.getBytes("gb2312"), "ISO8859-1" ) );