zoukankan      html  css  js  c++  java
  • 11-Upload&Download

    文件上传

    开发步骤

    1. 提供表单,允许用户通过表单选择文件进行上传

    • 表单必须是 POST 提交(表单默认为 GET 提交,请求参数不能超过 1KB)
    • 表单输入项必须有 name 属性(虽然文件表单输入项的name到后台没啥用),一个表单输入项如果没有 name 属性,Browser 是不会把它当作请求参数提交的。示例:<input type="file" name="file1"/>
    • <form> 加上 enctype="multipart/form-data" 属性

    2. 在 Servlet 中将上传的文件保存在 sever 的硬盘中

    • 由于没有提供原生 API,需要自己手动实现:先用request.getInputStream() 调用该方法获取包含请求正文的 ServletInputStream 对象,然后一大堆步骤,比如要拿到分隔符,然后分割请求正文 .... 很麻烦。所以,功能实现需要基于 apache 提供的 jar:commons-fileupload-1.2.1.jar 和 commons-io-1.4.jar。
    • 工作流程图示:
    • 对照图大致理解下代码:
      // 创建工厂
      DiskFileItemFactory factory = new DiskFileItemFactory();
      // 生产文件上传核心类
      ServletFileUpload fileUpload = new ServletFileUpload(factory);
      // 利用文件上传核心类解析request
      List<FileItem> list = fileUpload.parseRequest(request);
      // 遍历所有的FileItem
      for(FileItem item : list) {
          if(item.isFormField()) { // 当前是一个普通的字段项
              String name = item.getFieldName();
              String value = item.getString();
              System.out.println(name+" = "+value);
          } else { // 当前是一个文件上传项
              String fileName = item.getName();
              InputStream in = item.getInputStream();
              OutputStream out = new FileOutputStream(
                  getServletContext().getRealPath("upload/"+fileName));
              IOUtils.transfer(in, out);
              IOUtils.close(in,out);
          }
      }
      

    相关 API

    • DiskFileItemFactory
      DiskFileItemFactory(int sizeThreshold, File repository)
      DiskFileItemFactory()
          void setSizeThreshold(int sizeThreshold)
              设定内存缓冲区大小 [默认10KB]
          void setRepository(File repository)
              设定临时文件夹大小 [默认System.getProperty("java.io.tmpdir")]
      
    • ServletFileUpload
      static boolean isMultipartContent(HttpServletRequest request)
          判断上传表单是否为 multipart/form-data 类型
      List parseRequest(HttpServletRequest request)
          解析 request 对象,并把表单中的每一个输入项包装成一个FileItem 对象
          并返回一个保存了所有 FileItem 的 List
      setFileSizeMax(long fileSizeMax)
          设置单个上传文件的最大值;超过阈值抛 FileSizeLimitExceededException
      setSizeMax(long sizeMax)
          设置上传文件总量的最大值
      setHeaderEncoding(String encoding)
          设置编码格式,解决上传文件名乱码问题
      setProgressListener(ProgressListener pListener)
          实时监听文件上传状态 (注册监听要放在解析request之前进行!)
      
    • FileItem
      boolean isFormField() 判断FileItem是一个文件上传对象还是普通表单对象
      
      > 如果判断是一个普通表单对象
          String getFieldName() 获得普通表单对象的name属性
          String getString(String encoding) 获得普通表单对象的value属性,可用形参来解决乱码问题
      > 如果判断是一个文件上传对象
          String getName() 获得上传文件的文件名
          InputStream getInputStream() 获得上传文件的输入流
          void delete() 在关闭 FileItem 输入流后,删除临时文件
      

    JS 实现多文件上传

    每次动态增加一个文件上传输入框,都把它和删除按纽放置在一个单独的 <div> 中,并对删除按纽的 onclick 事件进行响应,使之删除删除按纽所在的 <div>

    moreUpload.jsp

    <html>
        <head>
        <title>多文件上传</title>
        <script>
            function addOne() {
                var fdiv = document.getElementById("fdiv");
                fdiv.innerHTML += "<div><input type='file' name='file' />"
                    + "<input type='button' id='delBtn' onclick='delOne(this)'"
                    + "value='删除'/><br></div>";
            }
    
            function delOne(btn) {
                btn.parentNode.parentNode.removeChild(btn.parentNode);
            }
        </script>
        </head>
        <body>
            <h1>文件上传</h1>
            <input type="button" id="addBtn" onclick="addOne()" value="加一个"/>
            <form action="${pageContext.request.contextPath }/servlet/UploadServlet2"
                    method="POST" enctype="multipart/form-data">
                描述信息1: <input type="text" name="desc1" />
                描述信息2: <input type="text" name="desc2" />
                <div id="fdiv"></div>
                <input type="submit" value="提交" />
            </form>
        </body>
    </html>
    

    上传文件保存

    1. 文件名
      为防止多用户上传相同文件名的文件,而导致文件覆盖的情况发生,文件上传程序应保证上传文件具有唯一文件名。可以使用一个表示通用唯一标识符 (UUID) 的类。 UUID 表示一个 128 位的值。
      static UUID randomUUID() 生成一个不重复的 128 位的二进制
      public String toString() 128 位二进制 -> 32 位十六进制
      
    2. 文件的存储结构
      为防止单个目录下文件过多,影响文件读写速度,处理上传文件的程序应根据可能的文件上传总量,选择合适的目录结构生成算法,将上传文件分散存储(比如,根据 hash 值来分目录存储)。
    3. 文件在web应用中的存放路径
      为保证服务器安全,上传文件应保存在应用程序的 WEB-INF 目录下,或者不受 web 服务器管理的目录。防止用户上传 JSP恶意入侵或访问其他用户上传的资源

    一个较完整的文件上传代码:

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        try {
            // 检查表单格式是否正确
            if(!ServletFileUpload.isMultipartContent(request))
                throw new RuntimeException("请用正确的表单格式上传");
    
            DiskFileItemFactory factory = new DiskFileItemFactory();
            factory.setSizeThreshold(100*1024);
            factory.setRepository(new File(getServletContext().getRealPath("WEB-INF/temp")));
            ServletFileUpload fileUpload = new ServletFileUpload(factory);
    
            // 设置单个文件的最大值
            fileUpload.setFileSizeMax(100*1024);
            // 设置上传文件总量的最大值
            fileUpload.setSizeMax(200*1024);
            // 设置编码集(解决上传文件名乱码问题)
            fileUpload.setHeaderEncoding("utf-8");
            // 设置文件上传监听
            // fileUpload.setProgressListener(new ProgressListener() {...});
    
            List<FileItem> list = fileUpload.parseRequest(request);
            for(FileItem item : list)
                if(item.isFormField()) {
                    String name = item.getFieldName();
                    String value = item.getString("utf-8");// 解决请求参数乱码
                    System.out.println(name+" = "+value);
                } else {
                    // ==== 存储 [上传文件] 前的准备工作 ====
                    String fileName = item.getName();
                    String uuidName = UUID.randomUUID().toString()+"_"+fileName;
                    // 根据 hash 值实现分目录存储
                    int hash = uuidName.hashCode();
                    String hashStr = Integer.toHexString(hash);
                    // 每一位代表一级目录, 一共可以有: 16^8 = 4294967296
                    char[] dirArr = hashStr.toCharArray();
                    String path = getServletContext().getRealPath("/WEB-INF/upload");
                    for(char c : dirArr) path += "/"+c;
                    // 系统若找不到指定目录,就应该先创建出这个层级目录
                    new File(path).mkdirs();
    
                    // ==== 存储 [上传文件] ====
                    InputStream in = item.getInputStream();
                    OutputStream out = new FileOutputStream(new File(path, uuidName));
                    IOUtils.transfer(in, out);
                    IOUtils.close(in, out);
                    item.delete();
                }
        } catch (FileSizeLimitExceededException e) {
            response.getWriter().write("单个文件不超过10M, 总大小不超过100M");
        } catch (FileUploadException e) {
            e.printStackTrace();
        }
    }
    

    文件上传监视

    fileUpload.setProgressListener(new ProgressListener() {
        Long beginTime = System.currentTimeMillis();
        @Override
        public void update(long pBytesRead, long pContentLength, int pItems) {
            BigDecimal br = new BigDecimal(pBytesRead).
                                divide(new BigDecimal(1024), 2,BigDecimal.ROUND_HALF_UP);
            BigDecimal cl = new BigDecimal(pContentLength)
                                .divide(new BigDecimal(1024), 2,BigDecimal.ROUND_HALF_UP);
            System.out.print("当前读取的是第"+pItems+"个field,总大小是"+cl+"KB,正在读取"+br+"KB,");
            // 剩余字节数
            BigDecimal rb = cl.subtract(br);
            System.out.print("剩余"+rb+"KB,");
            // 上传百分比
            // BigDecimal per = br.divide(cl,4,BigDecimal.ROUND_HALF_UP)
                                                    .multiply(new BigDecimal(100));
            // 结果成了:85.7600  应该先乘以100
            BigDecimal per = br.multiply(new BigDecimal(100)
                                                .divide(cl,4,BigDecimal.ROUND_HALF_UP));
            System.out.print("已经完成"+per+"%,");
            // 上传用时
            Long nowTime = System.currentTimeMillis();
            Long useTime = (nowTime - beginTime)/1000;
            System.out.print("已经用时"+useTime+"秒,");
            // 上传速度
            BigDecimal speed = new BigDecimal(0);
            if(useTime != 0)
                speed = br.divide(new BigDecimal(useTime),2,BigDecimal.ROUND_HALF_UP);
            System.out.print("上传速度为"+speed+"KB/s,");
            // 大致剩余时间
            BigDecimal rt = new BigDecimal(0);
            if(!speed.equals(new BigDecimal(0)))
                rt = rb.divide(speed,0,BigDecimal.ROUND_HALF_UP);
            System.out.println("剩余时间"+rt+"秒");
        }
    });
    

    文件下载

    public class DownloadServlet extends HttpServlet {
    
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            String fileName = request.getParameter("file");
            // 通知 Browser 以附件形式打开
            response.setHeader("Content-Disposition"
                , "attachment;filename=" + URLEncoder.encode(fileName,"utf-8"));
            // 通知 Browser 发送的是什么格式的数据
            response.setContentType(getServletContext().getMimeType(fileName)); // MIME类型
            InputStream in = new FileInputStream(getServletContext().getRealPath(fileName));
            OutputStream out = response.getOutputStream();
            IOUtils.transfer(in, out);
            IOUtils.close(in, out);
        }
    
        public void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            doGet(request, response);
        }
    }
    
    • 发送响应头 Content-Disposition
    • 文件名 URL 编码
    • 设置 MIME 类型

    exer:网盘

    功能分析

    • index.jsp:提供 [上传]、[下载列表]
    • upload.jsp:提供上传表单,允许用户选择文件进行上传
    • UploadServlet:① 保存上传的文件到服务器;② 在 DB 中保存文件相关信息
    • DownloadListServlet:查询数据库,列出所有可供下载的资源信息,存入 request 域后转发到 downloadList.jsp 做显示
    • downloadList.jsp:遍历 request 域中所有资源信息,提供下载链接
    • DownloadServlet:下载指定 id 的资源

    代码实现

    Table:netdisk

    Resource

    public class Resource implements Serializable {
        private int id;
        private String uuidName;
        private String realName;
        private String savePath;
        private String uploadTime;
        private String description;
        private String ip;
        ...
    }
    

    UploadServlet

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        if(!ServletFileUpload.isMultipartContent(request))
            throw new RuntimeException("请使用正确的表单格式上传文件");
        
        String upload = getServletContext().getRealPath("WEB-INF/upload");
        String temp = getServletContext().getRealPath("WEB-INF/temp");
        Map<String,String> paramMap = new HashMap<String,String>();
        paramMap.put("ip", request.getRemoteAddr());
        
        // 封装上传文件
        try {
            DiskFileItemFactory factory = new DiskFileItemFactory(1024,new File(temp));
            ServletFileUpload fileUpload = new ServletFileUpload(factory);
            fileUpload.setHeaderEncoding("utf-8");
            fileUpload.setFileSizeMax(100*1024*1024);
            List<FileItem> list = fileUpload.parseRequest(request);
            for(FileItem item : list) {
                if(item.isFormField()) {
                    String name = item.getFieldName();
                    String value = item.getString("utf-8");
                    paramMap.put(name, value);
                } else {
                    String realName = item.getName();
                    paramMap.put("realName", realName);
                    InputStream in = item.getInputStream();
                    String uuidName = UUID.randomUUID().toString()+"_"+realName;
                    paramMap.put("uuidName", uuidName);
                    int hash = uuidName.hashCode();
                    char[] dirArr = Integer.toHexString(hash).toCharArray();
                    String savePath = "/WEB-INF/upload";
                    for(char c : dirArr) {
                        upload += "/"+c;
                        savePath += "/"+c;
                    }
                    paramMap.put("savePath", savePath);
                    new File(upload).mkdirs();
                    OutputStream out = new FileOutputStream(new File(upload, uuidName));
                    IOUtils.transfer(in, out);
                    IOUtils.close(in, out);
                    item.delete();
                }
            }
            // 向数据库插入数据
            Resource r = new Resource();
            BeanUtils.populate(r, paramMap);
            String sql = "insert into netdisk values(null, ?, ?, ?, null, ?, ?)";
            QueryRunner runner = new QueryRunner(DaoUtils.getSource());
            runner.update(sql, r.getUuidName(), r.getRealName()
                    , r.getSavePath(), r.getDescription(), r.getIp());
            // 重定向回主页
            response.sendRedirect(request.getContextPath());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    

    DownloadListServlet

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 找出数据库中所有可供下载的资源信息
        String sql = "select * from netdisk";
        QueryRunner runner = new QueryRunner(DaoUtils.getSource());
        List<Resource> list = null;
        try {
            list = runner.query(sql, new BeanListHandler<Resource>(Resource.class));
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        // 存入 request 域中,带到 downloadList.jsp 做展示
        request.setAttribute("downloadList", list);
        request.getRequestDispatcher("/downloadList.jsp").forward(request, response);
    }
    

    downloadList.jsp

    <body>
        <div align="center">
            <h1>资源下载列表</h1>
            <table border="1">
                <tr>
                    <th>文件名称</th>
                    <th>上传时间</th>
                    <th>上传者IP</th>
                    <th>描述信息</th>
                    <th>可选操作</th>
                </tr>
                <c:forEach items="${requestScope.downloadList }" var="resource">
                    <tr>
                        <td><c:out value="${resource.realName }" /></td>
                        <td><c:out value="${resource.uploadTime }" /></td>
                        <td><c:out value="${resource.ip }" /></td>
                        <td><c:out value="${resource.description }" /></td>
                        <td><a href="${pageContext.request.contextPath }
                                /servlet/DownloadServlet?id=${resource.id }">下载</a></td>
                    </tr>
                </c:forEach>
            </table>
        </div>
    </body>
    

    DownloadServlet

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String id = request.getParameter("id");
        String sql = "select * from netdisk where id = ?";
        QueryRunner runner = new QueryRunner(DaoUtils.getSource());
        Resource resource = null;
    
        try {
            resource = runner.query(sql, new BeanHandler<Resource>(Resource.class),id);
        } catch (SQLException e) {
            e.printStackTrace();
            throws new RuntimeException(e);
        }
    
        if(resource!=null) {
            response.setHeader("content-disposition"
                , "attachment;filename=" + URLEncoder.encode(resource.getRealName(), "utf-8"));
            response.setContentType(getServletContext().getMimeType(resource.getRealName()));
            InputStream in = new FileInputStream(new File(getServletContext()
                .getRealPath(resource.getSavePath()),resource.getUuidName()));
            OutputStream out = response.getOutputStream();
            IOUtils.transfer(in,out);
            IOUtils.close(in,null);
        } else response.getWriter().write("资源不存在");
    }
    
  • 相关阅读:
    oracle基础~ash报告管理
    django基础-bootstarp
    django基础~admin入门
    django基础~jquery交互
    oracle基础~RAC-grid搭建
    Oracle基础~RAC搭建准备二
    oracle基础~RAC搭建准备一
    oracle基础~补丁安装
    环境准备、框架认识、新建maven项目和配置tomcat
    python线程问题、深浅拷贝、属性动态设置
  • 原文地址:https://www.cnblogs.com/liujiaqi1101/p/13388679.html
Copyright © 2011-2022 走看看