第十章 文件上传与下载

target

了解使用 JavaWeb方式的文件上传
掌握文件上传的表单设置
掌握使用 SpringMVC 进行文件上传
理解使用SpringMVC进行文件下载

文件上传你是任何一个项目都必不缺少的,抖音里面头像的上传,QQ里面视频的上传,拼多多里面商品详情介绍等,文件上传随处可见。下面介绍JavaWeb方式的原始上传和SpringMVC方式的上传。

1. 文件上传-JavaWeb方式

对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用Servlet获取上传文件的输入流然后再解析里面的请求参数是比较麻烦,所以一般选择采用apache的开源工具common-fileupload这个文件上传组件。common-fileupload是依赖于common-io这个包的,所以还需要下载这个包。

新建动态Web项目:FileUpload-1

在WebContent目录下新建上传表单:upload.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>




文件上传



    
上传用户:
上传文件:

新建 UploadServlet类处理文件上传:

package com.lee.servlet;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

@WebServlet("/upload")
public class UploadServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;
    private static final int FILE_SIZE = 10;// 单个文件最大值
    private static final String EXT_NAME = "gif,pdf,dmg,jpg,jpeg,png,bmp,swf,flv,mp3,wav,wma,wmv,mid,avi,mpg,asf,rm,rmvb,doc,docx,xls,xlsx,ppt,htm,html,txt,zip,rar,gz,bz2";// 定义允许上传的文件名
    private static final String savePath = "WEB-INF/upload";
    private static final String temPath = "WEB-INF/tem";
    
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 得到上传文件的保存目录,将上传文件存放在WEB-INF目录下,不允许外界直接访问,保证上传文件的安全
        String saveRealPath = this.getServletContext().getRealPath(savePath);
        createDir(saveRealPath);// 创建WEB-INF/upload目录

        // 上传时生成临时文件保存目录
        String saveTemPath = this.getServletContext().getRealPath(temPath);
        File temDir = createDir(saveTemPath);// 创建WEB-INF/tem文件夹

        String message = "";// 消息提醒

        String fileName = "";// 上传的文件名

        // 使用Apache文件上传组件处理文件上传步骤
        try {
            /**
             * 1.创建DiskFileItemFactory对象
             */
            DiskFileItemFactory factory = new DiskFileItemFactory();
            // 设置工厂的缓冲区大小,当上传的文件大小超过缓冲区大小时,就会生成一个临时文件存放在指定的临时目录之中
            // 设置缓冲区大小为100kb,如果不设置,默认缓冲区大小是10kb
            factory.setSizeThreshold(1024 * 100);
            // 设置上传时生成的临时文件的保存目录
            factory.setRepository(temDir);

            /**
             * 2.创建文件上传解析器
             */
            ServletFileUpload upload = new ServletFileUpload(factory);

            // 解决上传文件名的中文乱码问题
            upload.setHeaderEncoding("UTF-8");

            // 判断提交上来的数据是否是文件表单
            if (!ServletFileUpload.isMultipartContent(req)) {
                // 终止文件上传处理
                return;
            }
            // 获取文件表单中的所有文件
            List items = upload.parseRequest(req);
            for (FileItem item : items) {
                if (item.isFormField()) {// formfield中封装的是普通的输入项数据
                    String name = item.getFieldName();
                    // 解决输入项数据中文乱码问题
                    String value = item.getString("UTF-8");
                    // value = new String(value.getBytes("iso8859-1"),"UTF-8");
                    System.out.println(name + "=" + value);
                } else {// formfield中封装的是上传文件
                    fileName = item.getName();// 得到文件上传名
                    System.out.println("文件名:" + fileName);
                    if (fileName == null || fileName.trim().length() == 0) {
                        continue;
                    }

                    /**
                     * 注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的 如: C:\Users\H__D\Desktop\1.txt
                     * 而有些则是: 1.txt
                     */

                    // 处理获取到的上传文件的文件名的路径部分,只保留文件名部分
                    fileName = fileName.substring(fileName.lastIndexOf(File.separator) + 1);
                    // 得到上传文件的扩展名
                    String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();

                    // 通过扩展名判断上传文件是否合法
                    System.out.println("上传文件的扩展名是:" + fileExt);
                    if (!EXT_NAME.contains(fileExt)) {
                        System.out.println("不允许上传" + fileExt + "格式的文件!");
                        message += "文件:" + fileName + ",上传文件扩展名不允许是" + fileExt + "
"; continue; } // 检查文件大小 if (item.getSize() == 0 || item.getSize() > FILE_SIZE * 1024 * 1024) { message += "文件大小为:" + item.getSize() / 1024 / 1024 + "M,规定大小不超过" + FILE_SIZE + "M
"; continue; } // 得到存储的文件名 String saveFileName = makeFileName(fileName); // 获取item中上传文件的输入流 InputStream is = item.getInputStream(); // 创建文件输出流 FileOutputStream out = new FileOutputStream(saveRealPath + File.separator + saveFileName); // 创建一个缓冲区 byte[] b = new byte[1024]; int len = 0; while ((len = is.read(b)) > 0) { out.write(b, 0, len); } // 关闭流 out.close(); is.close(); // 删除临时文件 item.delete(); message += "文件:" + fileName + "上传成功!
"; } } } catch (Exception e) { System.out.println(e.getMessage()); message += fileName + "上传失败!"; } req.setAttribute("message", message); req.getRequestDispatcher("WEB-INF/views/message.jsp").forward(req, resp); } /** * 为防止文件覆盖现象,为上传文件产生一个唯一的文件名 * * @param fileName * @return */ private String makeFileName(String fileName) { return UUID.randomUUID().toString().replaceAll("-", "") + "_" + fileName; } /** * 创建目录 * * @param path */ private File createDir(String path) { File pathDir = new File(path); if (!pathDir.exists()) { // 创建WEB-INF/upload目录 pathDir.mkdirs(); } return pathDir; } }

在WEB-INF/views目录下新建消息页面:message.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>




提示


${message }


访问地址:http://localhost:8080/File/upload.jsp

此种方式可以上传一个或多个文件。

文件上传到服务器的位置"upload",该位置是指:workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps

几个细节:

①为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,比如放于WEB-INF目录下。
②为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名。
③要限制上传文件的最大值。
④要限制上传文件的类型,在收到上传文件名时,判断后缀名是否合法。
⑤表单的enctype="multipart/form-data" method="post"

2. 文件上传-SpringMVC方式

Spring MVC 框架的文件上传是基于 commons-fileupload 组件的文件上传,只不过 Spring MVC 框架在原有文件上传组件上做了进一步封装,简化了文件上传的代码实现,取消了不同上传组件上的编程差异。

commons-fileupload 组件依赖于 Apache 的另外一个项目(commons-io),因此需要导入commons-io的jar包。

Commons 是 Apache 开放源代码组织中的一个 Java子项目,该项目包括文件上传、命令行处理、数据库连接池、XML 配置文件处理等模块。fileupload 就是其中用来处理基于表单的文件上传的子项目,commons-fileupload 组件性能优良,并支持任意大小文件的上传。

2.1 表单属性

由于多数文件上传都是通过表单形式提交给后台服务器的,因此,要实现文件上传功能,就需要提供一个文件上传的表单。同时该表单必须满足以下3个条件:

  • form表单的method属性设置为post;
  • form表单的enctype属性设置为multipart/form-data
  • 提供的文件上传输入框。

表单的 enctype 属性指定的是表单数据的编码方式,该属性有以下 3 个值:

  • application/x-www-form-urlencoded:这是默认的编码方式,它只处理表单域里的 value 属性值。
  • multipart/form-data:该编码方式以二进制流的方式来处理表单数据,并将文件域指定文件的内容封装到请求参数里。
  • text/plain:该编码方式只有当表单的 action 属性为“mailto:”URL 的形式时才使用,主要适用于直接通过表单发送邮件的方式。

在HTML5之前,如果想要上传多个文件,必须使用多个元素。但是在HTML5中,在元素配置multiple属性即可进行多个文件的上传:


2.3 MultipartFile接口

在 Spring MVC 框架中上传文件时将文件相关信息及操作封装到 MultipartFile 对象中,因此开发者只需要使用 MultipartFile 类型声明模型类的一个属性即可对被上传文件进行操作。该接口具有如下方法:

名称 作用
byte[] getBytes() 以字节数组的形式返回文件的内容
String getContentType() 返回文件的内容类型
InputStream getInputStream() 返回一个InputStream,从中读取文件的内容
String getName() 返回请求参数的名称
String getOriginalFillename() 返回客户端提交的原始文件名称
long getSize() 返回文件的大小,单位为字节
boolean isEmpty() 判断被上传文件是否为空
void transferTo(File destination) 将上传文件保存到目标目录下

在上传文件时需要在配置文件中使用 Spring 的 org.springframework.web.multipart.commons.CommonsMultipartResolver 类配置 MultipartResolver 用于文件上传。

2.3 springmvc配置文件

当客户端form表单的enctype属性为multipart/form-data时,浏览器就会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP请求。Spring MVC为文件上传提供了直接的支持,这种支持是用即插即用的MultipartResolver实现的。

在Sring MVC中使用MultipartResolver也非常简单,只需要在Spring MVC配置文件springmvc.xml中定义MultipartResolver接口实现类即可,示例如下:



  
  
  
  
  

注意:

由于MultipartResolver实现类CommonsMultipartResolver内部是引用multipartResolver字符串获取该实现类对象并完成文件上传操作的,所以在配置CommonsMultipartResolver时必须指定该bean的id为multipartResolver。

由于MultipartResolver实现类CommonsMultipartResolver是Spring MVC内部通过Apache Commons FileUpload技术实现的。因此Spirng MVC的文件上传还需要依赖Apache Commons FileUpload的组件,即需要导入支持文件上传和下载的JAR包,具体如下:

  • commons-fileupload.jar
  • commons-io.jar

2.4 代码实现

新建动态Web项目:FileUpload-2

① 引入 jar

将一下jar 复制到lib文件夹下:

commons-fileupload-1.4.jar
commons-io-2.6.jar
commons-logging-1.2.jar
spring-aop-4.3.9.RELEASE.jar
spring-beans-4.3.9.RELEASE.jar
spring-context-4.3.9.RELEASE.jar
spring-core-4.3.9.RELEASE.jar
spring-expression-4.3.9.RELEASE.jar
spring-web-4.3.9.RELEASE.jar
spring-webmvc-4.3.9.RELEASE.jar

② 创建 web.xml 文件

在 WEB-INF 目录下创建 web.xml 文件。为防止中文乱码,需要在 web.xml 文件中添加字符编码过滤器。



  FileUpload-2
  
    index.jsp
  
  
        encodingFilter
        org.springframework.web.filter.CharacterEncodingFilter
        
            encoding
            UTF-8
        
    
    
        encodingFilter
        /*
    
  
  
    
        springDispatcherServlet
        org.springframework.web.servlet.DispatcherServlet
        
            contextConfigLocation
            classpath:springmvc.xml
        
        1
    

    
    
        springDispatcherServlet
        /
    

③ 创建 springmvc配置文件

在 src 下配置 springmvc.xml ,配置包扫描、视图解析器以及文件上传相关设置。




    
    

    
    
        
        
    

    
    
        
        
        
        
        
    

开发经验:一定要加上id="multipartResolver",否则会报错400。
④ 文件上传页面

在 WebContent 目录下创建 JSP页面 upload.jsp,在该页面中使用表单上传文件(可以一个或多个文件呢),具体代码如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>




SpringMVC方式上传



    
上传文件:

⑤ 创建控制器类

package com.lee.controller;

@Controller
public class UploadController {
    private static final String EXT_NAME = "gif,pdf,dmg,jpg,jpeg,png,bmp,swf,flv,mp3,wav,wma,wmv,mid,avi,mpg,asf,rm,rmvb,doc,docx,xls,xlsx,ppt,htm,html,txt,zip,rar,gz,bz2";// 定义允许上传的文件名
    private static final String SAVE_PATH = "WEB-INF/upload";

    @RequestMapping("/upload")
    public String fileUpload(@RequestParam("files") List files, HttpServletRequest req,
            Map map) {

        String message = "";
        // 对上传的文件进行解析
        if (files != null && files.size() > 0) {
            for (MultipartFile file : files) {
                // 获取文件的原始名字
                String filename = file.getOriginalFilename();
                // 设置文件保存目录
                String saveRealPath = req.getServletContext().getRealPath(SAVE_PATH);
                File saveDir = new File(saveRealPath);
                if (!saveDir.exists()) {
                    saveDir.mkdirs();
                }

                // 判断文件格式是否符合
                String extFileName = filename.substring(filename.lastIndexOf(".") + 1);
                if (!EXT_NAME.contains(extFileName)) {
                    message += filename + "格式不符合!
"; map.put("message", message); continue; } // 设置保存文件的名字:UUID或者时间进行重新命名 String newFileName = UUID.randomUUID().toString().replaceAll("-", "") + "_" + filename; // 使用multipartFile接口上传 try { File saveFile = new File(saveRealPath + File.separator + newFileName); file.transferTo(saveFile); message += filename + "上传成功!
"; } catch (Exception e) { e.printStackTrace(); message += filename + "上传失败!
"; } map.put("message", message); } } return "success"; } }

⑥ 创建成功显示页面

在 WEB-INF/views 目录下创建页面 success.jsp。具体代码如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
  pageEncoding="UTF-8"%>


  
    
    提示
  
  
    ${message }
  

部署项目,进行测试,可以选择一个或多个文件测试。

3. 文件下载

3.1 实现方法

实现文件下载有以下两种方法:

  • 通过超链接实现下载。
  • 利用程序编码实现下载。

通过超链接实现下载固然简单,但暴露了下载文件的真实位置,并且只能下载存放在 Web 应用程序所在的目录下的文件。

利用程序编码实现下载可以增加安全访问控制,还可以从任意位置提供下载的数据,可以将文件存放到 Web 应用程序以外的目录中,也可以将文件保存到数据库中。

利用程序实现下载需要设置两个报头:

① Web 服务器需要告诉浏览器其所输出内容的类型不是普通文本文件或 HTML 文件,而是一个要保存到本地的下载文件,这需要设置 Content-Type 的值为 application/x-msdownload。

② Web 服务器希望浏览器不直接处理相应的实体内容,而是由用户选择将相应的实体内容保存到一个文件中,这需要设置 Content-Disposition 报头。

该报头指定了接收程序处理数据内容的方式,在 HTTP 应用中只有 attachment 是标准方式,attachment 表示要求用户干预。在 attachment 后面还可以指定 filename 参数,该参数是服务器建议浏览器将实体内容保存到文件中的文件名称。

设置报头的示例如下:

response.setHeader("Content-Type", "application/x-msdownload");
response.setHeader("Content-Disposition", "attachment;filename="+filename);

3.2 代码实现

下面继续通过 FileUpload-2 应用讲述利用程序实现下载的过程,要求从(workspace.metadata.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\WEB-INF\uploadf)中下载文件,具体开发步骤如下:

① 编写控制器类

首先编写控制器类 FileDownController,在该类中有 3 个方法,即 show、down 和 toUTF8String。其中,show 方法获取被下载的文件名称;down 方法执行下载功能;toUTF8String 方法是下载保存时中文文件名的字符编码转换方法。

package com.lee.controller;

@Controller
public class FileDownController {

     /**
     * 显示要下载的文件
     */
    @RequestMapping("showDownFiles")
    public String show(HttpServletRequest request, Model model) {
        // 从 workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\下载
        String realpath = request.getServletContext()
                .getRealPath("WEB-INF/upload");
        File dir = new File(realpath);
        File files[] = dir.listFiles();
        // 获取该目录下的所有文件名
        ArrayList fileName = new ArrayList();
        for (int i = 0; i < files.length; i++) {
            fileName.add(files[i].getName());
        }
        model.addAttribute("files", fileName);
        return "showDownFiles";
    }
    /**
     * 执行下载
     */
    @RequestMapping("down")
    public String down(@RequestParam String filename,
            HttpServletRequest request, HttpServletResponse response) {
        String aFilePath = null; // 要下载的文件路径
        FileInputStream in = null; // 输入流
        ServletOutputStream out = null; // 输出流
        try {
            // 从workspace\.metadata\.plugins\org.eclipse.wst.server.core\
            // tmp0\wtpwebapps下载
            aFilePath = request.getServletContext().getRealPath("WEB-INF/upload");
            // 设置下载文件使用的报头
            response.setHeader("Content-Type", "application/x-msdownload");
            response.setHeader("Content-Disposition", "attachment; filename="
                    + toUTF8String(filename));
            // 读入文件
            in = new FileInputStream(aFilePath + File.separator+ filename);
            // 得到响应对象的输出流,用于向客户端输出二进制数据
            out = response.getOutputStream();
            out.flush();
            int aRead = 0;
            byte b[] = new byte[1024];
            while ((aRead = in.read(b)) != -1 & in != null) {
                out.write(b, 0, aRead);
            }
            out.flush();
            in.close();
            out.close();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 下载保存时中文文件名的字符编码转换方法
     */
    public String toUTF8String(String str) {
        StringBuffer sb = new StringBuffer();
        int len = str.length();
        for (int i = 0; i < len; i++) {
            // 取出字符中的每个字符
            char c = str.charAt(i);
            // Unicode码值为0~255时,不做处理
            if (c >= 0 && c <= 255) {
                sb.append(c);
            } else { // 转换 UTF-8 编码
                byte b[];
                try {
                    b = Character.toString(c).getBytes("UTF-8");
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                    b = null;
                }
                // 转换为%HH的字符串形式
                for (int j = 0; j < b.length; j++) {
                    int k = b[j];
                    if (k < 0) {
                        k &= 255;
                    }
                    sb.append("%" + Integer.toHexString(k).toUpperCase());
                }
            }
        }
        return sb.toString();
    }
}

② 创建文件列表页面

下载文件示例需要一个显示被下载文件的 JSP 页面 showDownFiles.jsp,在WEB-INF/views目录下编写,代码如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"
  pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

  
  Insert title here


  
      
被下载的文件名
${filename}

③ 测试下载功能

将应用部署到到 Tomcat 服务器并启动 Tomcat 服务器,然后通过地址"http://localhost:8080/FileUpload-2/showDownFiles"测试下载示例。

你可能感兴趣的:(第十章 文件上传与下载)