文件上传下载

时间:2021-06-05 20:57:59

文件上传下载(场景):

所谓文件上传下载就是将本地文件上传到服务器端,从服务器端下载文件到本地的过程。

例如目前网站需要上传头像、上传下载图片或网盘等功能都是利用文件上传下载功能实现的。

文件上传下载实际上是两步操作,

第一是文件上传,就是将本地文件上传到服务器端,实现文件多用户之间的共享,

第二是文件下载,就是将服务器端的文件下载到本地磁盘。

文件上传下载实现原理

文件上传下载

文件上传实现流程如下:

Ø  客户端浏览器通过文件浏览框,选择需要上传的文件内容(其中包括文件路径及文件内容)。

Ø  客户端浏览器通过点击上传按钮,将本地文件上传到服务器端。

Ø  服务器端通过程序接收本地文件内容,并将其保存在服务器端磁盘中。



 * 文件上传

   * 客户端

     * 文件上传页面(form)
       * 请求方式一定是POST.
       * 文件上传域(<input type='file'>)必须具有name属性.

       * 表单的enctype属性值设置为"multipart/form-data".

upload.jsp:

<body>
<form action="/19_upload/upload" method="post" enctype="multipart/form-data">
文件描述:<input type="text" name="filetext"><br>
<input type="file" name="upload"><br>
<input type="submit" value="上传">
</form>
</body>



     * 扩展:浏览器内核产品不同(不建议使用IE)
       * IE浏览器:IE6.0\7.0 IE8.0\9.0 IE10\11
       * 其他浏览器:Webkit(苹果)
         * 苹果浏览器
* 火狐浏览器:自主内核产品.
* 谷歌浏览器:自主内核产品.
       * 众多国内浏览器:
         * 百度浏览器:号称自主内核.
* 腾讯浏览器:号称自主内核.
* 遨游浏览器:号称自主内核.

* 360\搜狗\猎豹等...

文件上传下载

   * 服务器端

     * 导入工具包:commons-fileupload.jar和commons-io.jar包.

    commons-fileupload.组件工作流程:
文件上传下载

     * 实现的步骤:
       * 实例化DiskFileItemFactory工厂类.
       * 实例化ServletFileUpload类.
         ServletFileUpload upload = new ServletFileUpload(factory);
       * 利用upload的parseRequest(request)方法从Request对象中获取文件上传的内容(List集合).
       * 遍历获取到的List集合.
            * 如果是普通项 - 获取普通项的文本内容.
                  * isFormField():判断当前是否是普通项,true表示是.
                  * getFieldName():获取普通项的name属性值.
                  * getString():获取普通项的文本内容.
            * 如果是文件项
                 * getName():获取上传文件的名称.
                 * getInputStream():获取上传文件的输入流.
      * 通过保存的路径,创建文件的输出流.

      * 利用IOUtils.copy(inputStream,OutputStream)方法将上传文件进行保存.


public class UploadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 创建DiskFileItemFactory文件项工厂对象
DiskFileItemFactory factory = new DiskFileItemFactory();
// 通过工厂对象获取文件上传请求核心解析类ServletFileUpload
ServletFileUpload upload = new ServletFileUpload(factory);
try {
// 使用ServletFileUpload对应Request对象进行解析
List<FileItem> items = upload.parseRequest(request);
// 遍历每个fileItem
for (FileItem fileItem : items) {
// 判断fileItem是否是上传项
if (fileItem.isFormField()) { 通过FileItem.isFormField()方法判断当前是普通项还是文件项 返回布尔值:true:普通项;false:文件项
// 返回true:表示不是上传项
String fieldName = fileItem.getFieldName(); //获取普通项的name属性值
String str = fileItem.getString("utf-8"); //获取普通项的文本内容
System.out.println(fieldName+" : "+str);
}else{
// 返回false:表示是上传项
String name = fileItem.getName(); //获取文件项的上传文件名称
InputStream in = fileItem.getInputStream(); //获取文件项的上传文件输入流

String uploadPath = getServletContext().getRealPath("/upload");
OutputStream out = new FileOutputStream(new File(uploadPath, name));
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
out.close();
in.close();
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}
}

动态多文件上传:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP 'upload.jsp' starting page</title>
</head>
<script type="text/javascript">
function add(){
// 在div中添加上传输入项
document.getElementById("uploaddiv").innerHTML += "<div><input type='file' name='upload' /><input type='button' value='删除' onclick='del(this);' /></div>";
}
function del(obj){
// 传入obj 就是你点击 按钮对象
var div = obj.parentNode;
div.parentNode.removeChild(div);
}
</script>
<body>
<form action="/19_upload/upload" method="post" enctype="multipart/form-data">
<input type="button" value="上传文件" onclick="add();"/>
<div id="uploaddiv"></div>
<input type="submit" value="开始上传" />
</form>
</body>
</html>


   * 将文件上传至WEB-INF目录下:
         * WEN-INF目录外与WEB-INF目录中的区别:

               * WEN-INF目录外:在浏览器中可以访问的(安全低).

               * WEB-INF目录中:在浏览器中不能访问的.


获取文件上传路径的代码,应该修改为如下内容:

String uploadPath =getServletContext().getRealPath("/WEB-INF/upload");


     * 上传文件名称的处理:
       * 可能使用的浏览器版本过旧,获取到的文件上传的名称,而是选择上传文件的完整路径.
         C:\Users\JYL\Desktop\day0106\笔记\readme.txt
       * 解决办法:
         int index = fileName.lastIndexOf("\\");
         if(index >= 0){
         fileName.substring(index+1);

}



     * 上传文件的中文乱码问题:

       * 普通项的中文乱码:

        * 利用FileItem的getString(encoding)方法来解决.
          fileItem.getString("utf-8");

       * 文件项的中文乱码:

         * POST方式的请求参数:

           request.setCharacterEncoding("utf-8")



     * 上传文件同名的问题:

       * 问题:

                   默认情况下,上传多次同名的文件时,新的文件会覆盖旧的文件.

       * 解决:

                  将每个上传的文件名,提供一个唯一的标识(拼在文件名中).

                   //为每个上传的文件名称,增加一个随机名称值

                    fileName = UUID.randomUUID().toString()+fileName;

       * 注意:如果开发真实案例时,需要保存真实文件名称(给用户看的)和上传后处理的文件(真正做下载功能用的)名称.


     * 一个目录不能存放过多文件:

             * 问题:
                             * 长时间使用后,上传目录的体积多大.
                            * 上传目录保存的文件过多,查找文件过慢.
            * 解决:分级存储
                     * 按照用户名称分级存储.
                     * 按照日期时间分级存储.
                     * 指定一个上传子目录最大存储空间.

                     * 按照hashcode分级存储.

文件上传下载

可以将以上代码封装成一个工具类


      * 处理文件上传大小的限制:

       * 单个文件大小的限制:3M

           * 利用ServletFileUpload的setFileSizeMax(字节数).
           * 问题:如果上传的单个文件大小,大于限制的大小时:抛异常.

                  FileSizeLimitExceededException

              在实现多文件上传时,还需要设置上传文件的总大小。

              利用ServletFileUpload的setSizeMax(long)方法进行设置,其中参数表示设置的大小,单位为字节数,

              例如servletFileUpload.setSizeMax(1024*10)表示上限为10KB。

              一旦上传的文件大小超过限制大小时,会抛出FileUploadBase.SizeLimitExceededException异常

              ,可以捕获该异常后向页面输出相应的错误信息。具体实现代码如下:

public class UploadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
// 设置单个上传文件的大小
upload.setFileSizeMax(1024 * 10);
try {
List<FileItem> items = upload.parseRequest(request);
for (FileItem fileItem : items) {
if (fileItem.isFormField()) {
……
}else{
String name = fileItem.getName();
……
in.close();
}
}
} catch (FileUploadException e) {
if (e instanceof FileUploadBase.FileSizeLimitExceededException) {
// 在request中保存错误信息
request.setAttribute("msg", "上传失败!上传的文件超出了10KB!");
// 转发到index.jsp页面中!在index.jsp页面中需要使用${msg}来显示错误信息
request.getRequestDispatcher("/index.jsp").forward(request, response);
}
e.printStackTrace();
}
}
}



       * 上传文件总大小的限制:10M

          * upload.setSizeMax(1024*1024*10);

          * 抛异常:SizeLimitExceededException


        * 上传文件的缓存文件大小与临时目录:
        * 默认情况下:上传文件的输入流存储在服务器端的内存中(缓存).
       * 问题:
         * 当上传的单个文件过大时,导致服务器端的内存空间不足(性能下降)
       * 解决:
         * 指定上传文件的缓存大小.
         * 如果上传文件的缓存大于指定的缓存(内存中)大小,使用临时文件(硬盘中)的方式.
         * 如何实现:
         * 指定上传的缓存大小:

                 * factory.setSizeThreshold(1024*1024);


          * 设置上传临时目录:
            * factory.setRepository(new File(getServletContext().getRealPath("/tmp")));
            * 机制:当文件上传成功后,删除临时目录中的临时文件.

              fileItem.delete();

public class UploadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
// 设置单个上传文件的大小
upload.setSizeMax(1024 * 10);
try {
List<FileItem> items = upload.parseRequest(request);
for (FileItem fileItem : items) {
if (fileItem.isFormField()) {
……
}else{
String name = fileItem.getName();
……
in.close();
}
}
} catch (FileUploadException e) {
if (e instanceof FileUploadBase.SizeLimitExceededException) {
// 在request中保存错误信息
request.setAttribute("msg", "上传失败!上传的文件超出了10KB!");
// 转发到index.jsp页面中!在index.jsp页面中需要使用${msg}来显示错误信息
request.getRequestDispatcher("/index.jsp").forward(request, response);
}
e.printStackTrace();
}
}
}


     * 上传文件比较大时,上传的速度变慢?
       * 原因就是输入流与输出流的问题.
         * 输出流:new BufferedOutputStream(new FileOutputStream(new File(realPath,fileName)));

        * 输入流:new BufferedInputStream(fileItem.getInputStream());


     * 文件上传的进度条的功能:

          * 服务器端:监听文件上传的整个过程(从开始上传到上传结束)
           * 需要文件上传的监听器.
           upload.setProgressListener(new ProgressListener() {
                  /**

                    * update(long pBytesRead, long pContentLength, int pItems)


              *  * 作用:用于监听文件上传过程的状态信息.
              *  * 参数pBytesRead:到目前为止,已经读取上传文件的大小.
              *  * 参数pContentLength:上传文件的总大小.
              *  * 参数pItems:当前上传文件,是表单的第几个元素.

            *  计算以下四个结果:
                   *   * 已用时间:当前时间 - 开始时间
                   *   * 上传速度:已经上传大小 / 已用时间
                   *   * 剩余大小:总大小 – 已经上传大小
                   *   * 剩余时间:剩余大小 / 速度
           */
                                                                                                                                                           public void update(long pBytesRead, long pContentLength, int pItems) {
// 获取文件上传的当前时间
long currentTime = System.currentTimeMillis();
// 已用时间:当前时间 - 开始时间
long useTime = currentTime - startTime;
// 上传速度:已经上传大小 / 已用时间
long speed = pBytesRead / useTime;
// 剩余大小:总大小 – 已经上传大小
long restBytes = pContentLength - pBytesRead;
// 剩余时间:剩余大小 / 速度
long restTime = restBytes / speed;
System.out.println("上传速度为: "+speed+" , "+"当前还有 "+restBytes+" 没有上传完成, "+"还有 "+restTime+" 上传完成");
}
});


       * 客户端:添加进度条的内容(异步交互技术Ajax)




 * 文件下载

文件上传下载

文件下载实现流程如下:

Ø  客户端浏览器通过点击下载按钮,将服务器端保存的文件下载到本地磁盘。

Ø  服务器端通过程序将服务器端文件响应给客户端。

文件上传下载
   * 客户端
     * 显示文件下载列表(一):
       <h4><a href="${pageContext.request.contextPath }/downs/1.txt">1.txt</a></h4>
       <h4><a href="${pageContext.request.contextPath }/downs/2.xls">2.xls</a></h4>
       <h4><a href="${pageContext.request.contextPath }/downs/3.zip">3.zip</a></h4>
       <h4><a href="${pageContext.request.contextPath }/downs/4.jpg">4.jpg</a></h4>
       * 问题:
         * 如果浏览器本身支持下载文件的格式,并不提供下载,而是直接显示.
         * 如果浏览器本身不支持下载文件格式,提供下载功能.
     * 显示文件下载列表(二):
       <h4><a href="${pageContext.request.contextPath }/down?filename=1.txt">1.txt</a></h4>
       <h4><a href="${pageContext.request.contextPath }/downs?filename=2.xls">2.xls</a></h4>
       <h4><a href="${pageContext.request.contextPath }/downs?filename=3.zip">3.zip</a></h4>
       <h4><a href="${pageContext.request.contextPath }/downs?filename=4.jpg">4.jpg</a></h4>

   * 服务器端


 * 客户端:
   * 文件下载列表页面:
     * a标签的href属性,直接指定对应服务器端文件的路径.
       * 如果浏览器本身支持这种文件格式,直接打开.
     * a标签的href属性,指定一个服务器端的程序(Servlet)的路径.
 * 服务器端:
   * 服务器端接收客户端请求下载(包含下载的文件名称).
   * 查询数据库表(文件名称,文件存储路径).
   * 通过文件存储路径,找到对应要下载的文件(文件输入流).
   * 创建输出流(response.getOutputStream()方法).
   * 调用IOUtils.copy(inputStream,outputStream).
 * 注意:要想服务器端下载而不是显示必加:
   response.setContentType(getServletContext().getMimeType(filename));
   response.setHeader("Content-Disposition", "attachment;filename="+filename);


 * 解决下载中的中文乱码问题:
   * 解决请求参数中文乱码:
     filename = new String(filename.getBytes("ISO-8859-1"),"utf-8");
   * 解决下载文件名称中文乱码:
        String userAgent = request.getHeader("User-Agent");
if(userAgent.contains("MSIE")){
/ IE浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
}else{
// 其他浏览器
BASE64Encoder base64Encoder = new BASE64Encoder();
filename = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
}