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
- DiskFileItemFactoryDiskFileItemFactory(int sizeThreshold, File repository) DiskFileItemFactory() void setSizeThreshold(int sizeThreshold) 设定内存缓冲区大小 [默认10KB] void setRepository(File repository) 设定临时文件夹大小 [默认System.getProperty("java.io.tmpdir")]
- ServletFileUploadstatic 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之前进行!)
- FileItemboolean 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>
上传文件保存
- 文件名
 为防止多用户上传相同文件名的文件,而导致文件覆盖的情况发生,文件上传程序应保证上传文件具有唯一文件名。可以使用一个表示通用唯一标识符 (UUID) 的类。 UUID 表示一个 128 位的值。static UUID randomUUID() 生成一个不重复的 128 位的二进制 public String toString() 128 位二进制 -> 32 位十六进制
- 文件的存储结构
 为防止单个目录下文件过多,影响文件读写速度,处理上传文件的程序应根据可能的文件上传总量,选择合适的目录结构生成算法,将上传文件分散存储(比如,根据 hash 值来分目录存储)。
- 文件在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("资源不存在");
}

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号