第 11 章 文件上传

警告

DiskFileUpload已被废弃,例子部分需要重写。

上传文件时,同时传递普通字段,需要指定编码。

注意

http协议允许用户以请求形式将本地文档上传到服务器,这部分将涉及到二进制数据的处理,java开源社区提供了许多处理文件上传的工具包,所以我们仅仅介绍上传的原理流程和工具配置,不会重新制造轮子。

如果你不满足以下任一条件,请继续阅读,否则请跳过此后的部分,进入下一章:第 12 章 导出文件

  1. 了解http使用POST和GET发送请求的区别。

  2. 了解如何使用html表单上传文件。

  3. 了解使用commons-fileupload处理用户上传的文件。

11.1. 远程网盘

用户点击浏览选择需要上传的本地文件,然后点击提交上传到服务器。

文件列表中显示已上传文件的名称,大小和上传时间,可以点击文件名浏览或下载文件,也可以点击删除删除文件。

请看一下这里的temp和upload目录,commons-fileupload会把从请求中接收到的文件临时保存在temp目录下等待处理,我们可以使用write()方法将临时文件移动到我们指定的目录,也可以直接用delete()方法删除。

upload目录是上传文件的存放目录,上传成功的文件最后都会保存到这个目录下。

为了使用commons-fileupload处理上传文件,需要把两个依赖包放到WEB-INF/lib目录下。

这样就能在UploadServlet.java中使用commons-fileupload了,处理上传的save()方法如下:

public void save(HttpServletRequest request,HttpServletResponse response)
    throws Exception {
    String temp = getServletContext().getRealPath("/temp"); // 上传时存放临时文件的目录
    String uploadDir = getServletContext().getRealPath("/upload"); // 上传文件存放的目录
    DiskFileUpload diskFileUpload = new DiskFileUpload();
    diskFileUpload.setSizeMax(1*1024*1024); // 设置允许用户上传文件大小,单位:字节
    diskFileUpload.setSizeThreshold(4096);  // 设置最多只允许在内存中存储的数据,单位:字节
    diskFileUpload.setRepositoryPath(temp); // 设置一旦文件大小超过getSizeThreshold()的值时数据存放在硬盘的目录

    //开始读取上传信息
    List fileItems = diskFileUpload.parseRequest(request);
    Iterator iter = fileItems.iterator(); // 依次处理每个上传的文件

    while (iter.hasNext()) {
        FileItem item = (FileItem) iter.next(); // 忽略其他不是文件域的所有表单信息
        if (!item.isFormField()) {
            String name = item.getName(); // 获取上传文件名,包括路径
            name = name.substring(name.lastIndexOf("\\") + 1); // 从全路径中提取文件名
            long size = item.getSize();
            if (name != null && !name.equals("") && size != 0) {
                String filePath = System.currentTimeMillis() + "_" + name;
                item.write(new File(uploadDir, filePath));

                Upload upload = new Upload();
                upload.setFileName(name);
                upload.setFilePath("upload/" + filePath);
                upload.setFileSize(size);
                upload.setAddDate(new Date());

                list.add(upload);
            }
        }
    }
    response.sendRedirect("index.jsp");
}
        

getServletPath().getRealPath("/temp")将得到“tomcat安装目录 + /webapps + /temp”,这是我们获得服务器下目录下唯一方法,得到了这个完整路径后才可以决定将文件保存到什么地方。

处理文件上传的第一步是创建一个DiskFileUpload,为它设置临时目录,文件大小限制,内存缓存的大小。

得到DiskFileUpload实例后,调用parseRequest(request)解析请求,解析的结果是一个列表,因为我们可能上传多个问题见。

现在可以循环得到的列表处理每一个FileItem,如果isFormField()返回false,表示当前FileItem对应一个上传的文件,下面就能从FileItem中获得文件名和文件大小,最后调用write()方法写入upload目录下。

调用write()之后,temp目录下对应的临时文件自动会被删除,如果想保存文件的具体信息,还需要进行另外的处理,这里我们使用的是一个自定义javabean,每次上传成功后会创建一个Upload,将文件名,文件保存的路径,文件大小,上传时间加入UploadServlet中定义的list变量中,下次执行upload.do?method=list请求的时候会将其中保存的数据显示到list.jsp中。当然如果服务器重启后内存中的数据就会丢失,实际开发时我们需要将上传信息保存到数据库中。

11.2. 浏览器部分的设置

文件上传需要客户端与服务器端配合工作,客户端的浏览器必须将文件内容附加到http协议请求中,这样服务器才能处理。

浏览器端有几点需要注意的。

  1. 使用method="GET"。

    表单默认使用method="GET"提交请求,GET方式的请求是没有请求体(body)的,所有参数都将附加到url后传递给服务器。

    文件上传需要将二进制数据放到请求体(body)中,所以我们必须指定表单使用method="POST"。

  2. 为表单设置enctype="multipart/form-data"。

    不设置enctype的情况表单只会把文件名传递到服务器,enctype的效果是把本地文件以二进制的形式附加到请求的body中,供服务器接收解析。