So Easy系列之文件上传下载教程
- 文件上传下载概述
- 什么是文件上传下载
所谓文件上传下载就是将本地文件上传到服务器端,从服务器端下载文件到本地的过程。例如目前网站需要上传头像、上传下载图片或网盘等功能都是利用文件上传下载功能实现的。
文件上传下载实际上是两步操作,第一是文件上传,就是将本地文件上传到服务器端,实现文件多用户之间的共享,第二是文件下载,就是将服务器端的文件下载到本地磁盘。
下面就文件上传与下载功能,分别学习。
-
- 文件上传下载实现原理
首先,需要知道文件是如何实现上传及下载的。文件上传及下载实现原理如下:
文件上传实现原理分析
文件上传实现流程如下:
- 客户端浏览器通过文件浏览框,选择需要上传的文件内容(其中包括文件路径及文件内容)。
- 客户端浏览器通过点击上传按钮,将本地文件上传到服务器端。
- 服务器端通过程序接收本地文件内容,并将其保存在服务器端磁盘中。
文件下载实现原理分析
文件下载实现流程如下:
- 客户端浏览器通过点击下载按钮,将服务器端保存的文件下载到本地磁盘。
- 服务器端通过程序将服务器端文件响应给客户端。
- 文件上传实现
-
- 文件上传客户端页面实现
在Web应用程序中实现文件上传功能,只需要在客户端页面中添加需要上传输入项,在服务器端Servlet中读取上传文件的数据,并保存在服务器端硬盘中即可。
客户端浏览器页面实现文件上传功能,具体代码如下:
需要注意的是:
- <input type=”file”>标签必须指定name属性值,否则需要上传的文件数据是不会上传至服务器端。
- 完成文件上传功能的表单的请求类型必须是POST方式。
- 完成文件上传功能的表单的enctype属性值设置为“multipart/form-data”,该值的作用是将需要上传的文件数据添加到Http请求体中,并使用MIME协议对上传的文件进行描述。
- commons-fileupload工具
完成客户端的文件上传功能之后,主要是在服务器端完成接收上传文件的数据内容。为了方便实现文件上传逻辑,可以使用第三方提供的文件上传包,具体如下:
- jsp-smartupload.jar:使用JSP模型一时使用的,目前基本不再使用。
- commons-fileupload.jar:由Apache基金会提供的,用来实现Java环境下的文件上传功能。
- Servlet 3.0规范中提供对文件上传的支持。
commons-fileupload组件的官网地址:http://commons.apache.org/proper/commons-fileupload/。需要注意的是:在使用commons-fileupload组件时,需要依赖于commons-io包。commons-fileupload组件工作流程如下:
如何使用commons-fileupload组件实现文件上传功能,可以参考其官网的User Guide内容。
- 创建DiskFileItemFactory文件项工厂对象。
- 通过工厂对象获取文件上传请求核心解析类ServletFileUpload。
- 使用ServletFileUpload对应Request对象进行解析。
- 遍历每个fileItem,判断是否为上传项。
- IOUtils.copy(inputStream,OutputStream)(将上传的数据拷贝到服务器的硬盘上的简单方法)
具体实现代码如下:
-
- 动态多文件上传表单
上述案例实现的是单文件上传,如果想实现多文件上传功能的话,服务器端的逻辑是一样的,也就是说,只需要在客户端页面实现多文件上传控件即可。动态实现多文件上传表单代码如下:
-
- 上传文件至WEB-INF目录
到目前步骤,已经可以成功从客户端浏览器向服务器端上传文件。但是上传的路径存在一些问题,上述上传路径是自定义的文件夹,而上传至这种自定义的文件夹后,通过浏览器可以正常访问,这是非常危险的。
例如一个用户上传一个JSP页面,然后通过浏览器访问该JSP页面,而该JSP页面中可以包含一些恶意代码。这时如果允许用户运行该JSP页面的话,可能会对服务器端造成很大影响。
所以,通常情况下,会将上传目录创建在Web工程的WEB-INF目录下。因为该目录下的内容,是无法通过浏览器访问到的。
获取文件上传路径的代码,应该修改为如下内容:
-
- 上传文件名称的处理
对于上传文件的名称,可能是文件的完整路径,例如:C:\upload\aaa.jpg。在服务器端只需要保存其上传文件的名称即可,所以需要对上传文件的名称进行进一步地处理,具体处理代码如下:
目前绝大多数的浏览器都不存在这个问题,仅仅只有一些比较老的浏览器版本存在,例如IE6.0版本。为保证兼容更多浏览器产品,这个问题依旧需要解决。
-
- 上传文件中文乱码问题
如果现在上传文件的名称为中文的话,会引起中文乱码问题。commons-fileupload组件为解决中文乱码问题提供了两种解决方案,如下:
- 利用Request对象的setCharacterEncoding(“UTF-8”)方法,该方法尽量编写在Servlet的doGet()或doPost()方法的顶端。
- 利用ServletFileUpload类提供的setHeaderEncdoing(“UTF-8”)方法来解决。
一般情况下,不关心上传文件的内容,因为上传文件会保存在服务器端的磁盘中。但是,如果需要在控制台打印上传文件的内容,而刚好该上传文件的内容中包含中文的话,可以使用FileItem的getString(“UTF-8”)来处理编码。
-
- 上传文件同名问题的处理
如果同一个用户上传多个同名的文件,默认情况下会出现被覆盖的情况,即前一次上传的文件会被后一次上传的文件覆盖。而这种情况是不希望看到的,解决这个问题的方法就是可以为每一个上传的文件名称增加UUID,因为UUID类的randomUUID()可以生成一个唯一标识符。具体做法如下:
-
- 一个目录不能存放过多文件
如果上传文件过多时,会导致上传目录中的文件过多,内容过大。这时可以考虑将不同文件存储在不同的目录中,而生成不同目录的规则参考如下:
- 按照上传时间进行目录分离,例如2014-12-12为一个目录。
- 按照上传用户进行目录分离,为每一个用户创建一个上传目录。
- 按照固定数量进行目录分离,设定当一个上传目录包含文件超过指定数量,创建新的上传目录。
- 按照唯一文件名的hashcode 进行目录分离。
这里以hashcode进行目录分离方式为例演示,具体思路如下:
- 使用UUID类的randomUUID()方法生成唯一标识符。
其值为2dab369c-1e4f-4e58-8b61-13c7aef855b0。
- 通过hashCode()方法获取其唯一标识符的hashcode值。
其值为:166846237,转换成二进制后的值为:1001111100011101111100011101。
- 按照每4位值“与”二进制1111(F)后,生成一级目录。
- 以此类推,每4位值“与”二进制1111后,生成一个级别的目录。
根据上述步骤,可以编写一个按照hashcode方式生成目录的工具类,具体代码如下:
上述程序代码可以改写如下:
-
- 上传单个文件的大小限制
上传文件时,用户可能上传非常大的文件,可能导致上传时占用过多资源。所以,对于用户上传的单个文件大小,应做出相应限制。利用ServletFileUpload的setFileSizeMax(long)方法进行设置,其中参数表示设置的大小,单位为字节数,例如servletFileUpload.setFileSizeMax(1024*10)表示上限为10KB。
一旦上传的单个文件大小超过限制大小时,会抛出FileUploadBase.FileSizeLimitExceededException异常,可以捕获该异常后向页面输出相应的错误信息。具体实现代码如下:
-
- 上传文件的总大小的限制
在实现多文件上传时,还需要设置上传文件的总大小。利用ServletFileUpload的setSizeMax(long)方法进行设置,其中参数表示设置的大小,单位为字节数,例如servletFileUpload. setSizeMax(1024*10)表示上限为10KB。
一旦上传的文件大小超过限制大小时,会抛出FileUploadBase.SizeLimitExceededException异常,可以捕获该异常后向页面输出相应的错误信息。具体实现代码如下:
-
- 文件缓存大小与临时目录
一般情况下,上传文件默认都是先存储在内存中,然后在拷贝到服务器端的磁盘中。但是这样会有一些问题出现,例如单个文件过大时,占用服务器端资源会过多,导致服务器性能变差。这时可以通过手动设置文件缓存大小和上传文件的临时目录来解决。如果不设置上传的文件缓存大小,默认值为10KB,其表示如果上传文件小于10KB的话,会先存储在服务器端的内存中,如果上传文件大小大于10KB的话,会先存储在服务器端默认指定的临时目录中,而上传文件的默认临时目录为System.getProperty("java.io.tmpdir")。
手动修改上传文件缓存大小及临时目录的方式如下:
- 手动修改上传文件缓存大小:DiskFileItemFactory.setSizeThreshold(缓存字节数);
- 手动修改上传文件临时目录:
DiskFileItemFactory.setRepository(new File(getServletContext().getRealPath(临时目录相对路径)));
具体实现代码如下:
想要删除临时目录下的临时文件的话,只需要调用FileItem的delete()方法即可。
-
- 文件上传进度监听器
目前大部分具有文件上传功能的,在文件上传过程中,可以实时看到上传进度。可以使用ServletFileUpload提供的setProgressListener()方法实现,在客户端配置Ajax技术即可实现。由于目前没有掌握Ajax异步交互技术,所以只能在服务器端完成查看进度功能。
其中除使用setProgressListener()方法实现外,还需要计算如下几个结果:
- 已用时间:当前时间 – 开始时间
- 速度:已经上传大小 / 已用时间
- 剩余大小:总大小 – 已经上传大小
- 剩余时间:剩余大小 / 速度
根据上述内容,具体查看上传进度功能如下:
- 文件下载实现
-
- 实现文件下载
实现文件上传功能后,需要做的就是文件下载功能。文件下载不需要第三方组件支持,自定义完成即可,具体操作步骤如下:
- 创建一个JSP页面用于显示下载文件列表。
- 创建一个Servlet用于文件下载功能。
- 配置Web工程的web.xml文件。
-
- 中文乱码解决
下载文件功能实现后,还需要解决中文乱码问题。由于下载页面中的文件名是使用GET方式提交请求的,所以可以使用如下代码解决:
而上述代码只能解决服务器端接收客户端请求时参数的中文乱码问题,但是点击文件下载时的文件名称依旧是乱码的。这个问题是由于浏览器本身的问题,不同浏览器的解决方式不同:
- IE浏览器:使用URL编码。
- 其他浏览器,使用BASE64编码。
可以通过Http请求协议的请求头中的“User-Agent”内容,判断客户端当前使用的浏览器是哪个产品。
- 文件上传下载案例
通过网盘案例实现文件上传和下载功能,并且将文件上传相关信息保存到MySQL数据库表中。该案例实现原理分析如下:
MySQL数据库建表语句如下:
创建一个JSP页面用于网盘案例的主页面(提供文件上传功能和文件下载列表功能)。
-
- 文件上传功能
首先实现网盘中的文件上传功能,具体实现步骤如下:
- 创建一个JSP页面用于文件上传功能。
- 创建一个Servlet用于处理文件上传功能。
- 配置Web工程的web.xml文件。
- 创建UploadUtils工具类用于封装文件上传依赖逻辑。
- 创建一个JavaBean用于封装保存数据库相关信息。
-
- 文件下载功能
然后实现文件下载功能,具体实现步骤如下:
- 创建一个Servlet用于显示下载文件列表。
- 配置Web工程的web.xml文件。
- 创建一个JSP页面用于显示文件下载列表。
- 创建一个Servlet用于文件下载功能。
- 配置Web工程的web.xml文件。