前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >WEB:文件上传 —— 看这篇就够了

WEB:文件上传 —— 看这篇就够了

作者头像
WEBJ2EE
发布2019-07-30 15:22:39
5.6K0
发布2019-07-30 15:22:39
举报
文章被收录于专栏:WebJ2EEWebJ2EE

文件上传知识体系

如下图

1. 协议规范(RFC 1867)

HTML 表单最初只支持 application/x-www-form-urlencoded 形式编码(key=value&key=value...),但它不适合用于传输二进制数据(文件)或者包含非ASCII字符的数据。所以 multipart/form-data 就诞生了,专门用于传输文件。

  • HTML 的二进制文件传输特性,最初在《RFC 1867:Form-based File Upload in HTML》中定义。
  • <input> 扩充 type="file" 类型, 用于实现文件选择;
  • <form> 扩充 enctype="multipart/form-data" 编码形式,用于支持文件传输;
代码语言:javascript
复制
<%@page pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
        <form method="post" action="./recvfile"
              enctype="multipart/form-data">
            <label>文本域:</label>
            <input type="text" name="textfield"/>
            <br/>

            <label>单文件:</label>
            <input type="file" name="single"/>
            <br/>

            <label>多文件:</label>
            <input type="file" name="multi" multiple/>
            <br/>

            <label>没文件:</label>
            <input type="file" name="empty"/>
            <br/>

            <button type="submit">提交</button>
        </form>
    </body>
</html>

2. 文件上传请求响应

2.1. Servlet 3.x(MultipartConfig)

Servlet 3.x 大法好,无需插件,就能处理上传的文件。

代码语言:javascript
复制
import org.apache.commons.io.IOUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Iterator;

@MultipartConfig(
        fileSizeThreshold = 512, // 超过这个值,就暂存到磁盘
        location = "d:/temp", // 暂存区
        maxFileSize = 1024, // 请求中单个文件的最大尺寸
        maxRequestSize = 2048 // 请求的最大尺寸
)
@WebServlet(name = "RecvFile", urlPatterns = {"/recvfile"})
public class RecvFile extends javax.servlet.http.HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
            throws ServletException, IOException {
        Collection<Part> parts = request.getParts();
        Iterator<Part> itor = parts.iterator();
        while (itor.hasNext()) {
            Part part = itor.next();
            String fieldName = part.getName(); // 字段名
            String fileName = part.getSubmittedFileName(); // 文件名(注:此接口Servlet 3.1才有)
            String fileType = part.getContentType();
            String fileContent = new String(
                    IOUtils.toByteArray(
                            part.getInputStream()
                    ),
                    Charset.forName("UTF-8")
            );
            System.out.format(
                    "field:%s, fileName:%s, type:%s, content:%s\n",
                    fieldName, fileName, fileType, fileContent);
        }
    }
}
代码语言:javascript
复制
field:textfield, fileName:null, type:null, content:琦玉
field:single, fileName:杰洛斯.txt, type:text/plain, content:谢谢您为我解惑,老师的战斗为我指明了道路,强大的象征,终极目标的所在,我也要到那里去。
field:multi, fileName:琦玉1.txt, type:text/plain, content:没有什么是一拳解决不了的,如果有,那就两拳。
field:multi, fileName:琦玉2.txt, type:text/plain, content:咱回吧。
field:empty, fileName:, type:application/octet-stream, content:

2.2. Apache Commons Upload

Servlet 2.x 环境自身无法方便的处理文件上传请求,第三方工具 Apache Commons Upload 则是最好的选择。

关键依赖:

代码语言:javascript
复制
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.9</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

代码示例:

代码语言:javascript
复制
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.List;

@WebServlet(name = "ApacheRecvFile", urlPatterns = {"/apacherecvfile"})
public class ApacheRecvFile extends javax.servlet.http.HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
            throws ServletException, IOException {

        request.setCharacterEncoding("UTF-8");

        // 检测是否为文件上传请求
        if (!ServletFileUpload.isMultipartContent(request)) {
            throw new RuntimeException("不是文件上传请求!");
        }


        // 配置上传文件缓存策略
        // 1. SizeThreshold: 缓存文件大小阈值
        //     a.上传文件小于此阈值,暂存于内存;
        //     b.上传文件大于此阈值,暂存于磁盘;
        // 2. Repository: 上传文件暂存于磁盘时的目录;
        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setSizeThreshold(512); // 默认值:10240
        factory.setRepository(new File("d:/temp"));

        // 文件上传相关参数
        // 1. FileSizeMax: 限制请求中单个文件大小
        // 2. SizeMax: 限制请求的总大小
        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setFileSizeMax(1024); // 默认:-1,无限制
        upload.setSizeMax(2048); // 默认:-1,无限制

        // 分析请求
        List<FileItem> items = null;
        try {
            items = upload.parseRequest(request);
        } catch (FileUploadException e) {
            throw new RuntimeException(e);
        }
        Iterator<FileItem> iter = items.iterator();
        while (iter.hasNext()) {
            FileItem item = iter.next();
            if (item.isFormField()) {
                // 普通字段
                String fieldName = item.getFieldName(); //字段名
                String fileContent = item.getString("UTF-8");//字段值

                System.out.format(
                        "field:%s, content:%s\n",
                        fieldName, fileContent);
            } else {
                // 文件字段
                String fieldName = item.getFieldName(); // 字段名
                String fileName = item.getName(); // 文件名
                String fileType = item.getContentType();
                String fileContent = new String(
                        IOUtils.toByteArray(
                                item.getInputStream()
                        ),
                        Charset.forName("UTF-8")
                );

                System.out.format(
                        "field:%s, fileName:%s, type:%s, content:%s\n",
                        fieldName, fileName, fileType, fileContent);
            }
        }
    }
}
代码语言:javascript
复制
field:textfield, content:琦玉
field:single, fileName:D:\杰洛斯.txt, type:text/plain, content:谢谢您为我解惑,老师的战斗为我指明了道路,强大的象征,终极目标的所在,我也要到那里去。
field:multi, fileName:D:\\琦玉1.txt, type:text/plain, content:没有什么是一拳解决不了的,如果有,那就两拳。
field:multi, fileName:D:\\琦玉2.txt, type:text/plain, content:咱回吧。
field:empty, fileName:, type:application/octet-stream, content:

2.3. Spring MVC

Spring MVC 是一个分层的 Java Web 开发框架。Spring MVC的核心元素就是 Dispatcher Servlet,负责处理所有请求,但 DispatcherServlet 并没有实现任何解析 multipart 请求数据的功能。它将该任务委托给了Spring 中 MultipartResolver 策略接口的实现,通过这个实现类来解析 multipart 请求中的内容。Spring内置了两个 MultipartResolver 的实现供:

  1. CommonsMultipartResolver:依赖 Apache Commons Upload 解析 multipart 请求。
  2. StandardServletMultipartResolver:依赖 Servlet 3.x 对 multipart 请求的原生支持。

2.3.1 CommonsMultipartResolver 示例

关键依赖:

代码语言:javascript
复制
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.1.8.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

代码示例:web.xml

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         id="WebApp_ID" version="3.0">
    <display-name>xupload</display-name>

    <servlet>
        <servlet-name>xupload</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>xupload</servlet-name>
        <url-pattern>/mvc/*</url-pattern>
    </servlet-mapping>


    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

代码示例:springmvc.xml

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
            https://www.springframework.org/schema/context/spring-context.xsd">
    
    <context:component-scan base-package="webj2ee"/>

    <context:annotation-config/>

    <bean id="multipartResolver"
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
          p:defaultEncoding="UTF-8"
          p:maxInMemorySize="512"
          p:maxUploadSizePerFile="1048576"
          p:maxUploadSize="2097152" />
</beans>

代码示例:SpringMVCApacheCommonsUploadController

代码语言:javascript
复制
package webj2ee;

import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.List;

@RestController
public class SpringMVCApacheCommonsUploadController {
    @RequestMapping(value = "/recvfile")
    public void handleUpload(HttpServletRequest request) throws IOException {

        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;

        Iterator<String> fileNames = multipartRequest.getFileNames();
        while (fileNames.hasNext()) {
            String fieldName = fileNames.next();
            List<MultipartFile> files = multipartRequest.getFiles(fieldName);
            for (MultipartFile file : files) {

                String fileName = file.getOriginalFilename();
                String fileType = file.getContentType();
                String fileContent = new String(
                        IOUtils.toByteArray(
                                file.getInputStream()
                        ),
                        Charset.forName("UTF-8")
                );
                System.out.format(
                        "field:%s, fileName:%s, type:%s, content:%s\n",
                        fieldName, fileName, fileType, fileContent);
            }
        }
    }
}

2.3.2 StandardServletMultipartResolver 示例

与 CommonsMultipartResolver 示例相比,

只有 web.xml、springmvc.xml 略有差异;

代码示例:web.xml

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                            http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
        id="WebApp_ID" version="3.1">
    <display-name>xupload</display-name>

    <servlet>
        <servlet-name>xupload</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <multipart-config>
            <location>d:/temp</location>
            <max-file-size>1024</max-file-size>
            <max-request-size>2048</max-request-size>
            <file-size-threshold>512</file-size-threshold>
        </multipart-config>
    </servlet>

    <servlet-mapping>
        <servlet-name>xupload</servlet-name>
        <url-pattern>/mvc/*</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

代码示例:springmvc.xml

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
            https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="webj2ee"/>

    <context:annotation-config/>

    <bean id="multipartResolver"
          class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>
</beans>

3. 文件上传请求发起

3.1. Server 端发起(HttpClient)

应用场景:Server 端请求转发;

关键依赖:

代码语言:javascript
复制
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.9</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpmime</artifactId>
    <version>4.5.9</version>
</dependency>

代码示例:

代码语言:javascript
复制
package webj2ee;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;

public class SendMultiPartRequest {
    public static void main(String[] args) {
        // 构造连接池
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(4);
        cm.setDefaultMaxPerRoute(2);

        // CloseableHttpClient
        CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(cm).build();

        // 构造 MultiPart 请求实体
        MultipartEntityBuilder mpEntityBuilder = MultipartEntityBuilder.create();
        mpEntityBuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); // 注意字符集
        mpEntityBuilder.setCharset(Charset.forName("UTF-8"));
        mpEntityBuilder.addTextBody("text", "这是普通文本字段", ContentType.TEXT_PLAIN.withCharset("UTF-8")); // MultiPart 普通字段
        mpEntityBuilder.addBinaryBody("file1", new File("D:/杰洛斯.txt")); // Mulitpart 文件字段
        mpEntityBuilder.addBinaryBody("file2", new File("D:/琦玉1.txt"));
        HttpEntity entity = mpEntityBuilder.build();

        // 构造 POST 请求
        HttpPost httpPost = new HttpPost("http://localhost:8080/xupload/apacherecvfile");
        httpPost.setEntity(entity);

        // 发送请求
        try {
            CloseableHttpResponse closeableResponse = httpclient.execute(httpPost);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

注意事项:注意设置字符集,小心乱码;

3.2. 客户端发起 —— Flash(Uploadify)

Flash(Uploadify)的唯一价值就是增强了 IE7、8、9 的文件上传能力。如果你没办法甩开IE 这个小垃圾(特别是低版本IE),而且还想实现丰富的文件上传功能,Flash(Uploadify)是你唯一的选择。

优点:

a. 兼容IE7、IE8、IE9 b. 支持上传完成回调机制; c. 支持多选文件上传; d. 支持筛选上传文件类型; e. 支持限定上传文件尺寸; f. 支持文件上传进度监控;

缺点:

a. 要求客户端安装 Flash 控件; b. Cookie 在 Safari 环境下不能正常发送;

图:官方对 Session Cookie 问题的说明

代码示例:

代码语言:javascript
复制
<%@ page language="java" pageEncoding="UTF-8"%>
<%@ page session="false" %>
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <script type="text/javascript" src="./jquery-1.12.4.js"></script>
  
  <!-- 引入 uploadify 组件 -->
  <script type="text/javascript" src="./uploadify/jquery.uploadify.js"></script>
  <link href="./uploadify/uploadify.css" rel="stylesheet" type="text/css"/>
</head>
<body>
  <div id="myMultiFileUpload"></div>

  <script>
  $("#myMultiFileUpload").uploadify({
    swf             : './uploadify/uploadify.swf',   // 指明flash插件路径
    uploader        : './apacherecvfile',       // 文件上传请求地址
    width           : 120,
    height          : 30,
    fileObjName      : "myfile",           // 相当于 <input type="file" name="myfile"
    buttonText      : "选择文件",
    removeCompleted : false,
    fileTypeExts  : "*.png;*.jpg;*.txt",         // 限制可选的文件类型
    onUploadComplete: function(file) {        // 每个文件上传成功的回调
      console.log('[' + file.name + '] upload complete.');
    }
  });
</script>
</body>
</html>

效果展示:

3.3. 客户端发起 —— Form 表单上传

用 Form 表单上传文件,是最传统的方法,优点就是兼容性特别好。

优点:兼容性好,不需要插件,浏览器原生支持

老古董(IE7/8/9): a. 支持上传完成回调机制; b. × 支持多选文件上传; c. × 支持筛选上传文件类型; d. × 支持限定上传文件尺寸; e. × 支持文件上传进度监控; 现代浏览器(>=IE10、Chrome、Firefox): a. 支持上传完成回调机制; b. 支持多选文件上传; c. 支持筛选上传文件类型; d. × 支持限定上传文件尺寸; e. × 支持文件上传进度监控;

缺点:低版本浏览器上,能力偏弱;

代码示例:form_upload_ie8_ie9.jsp

代码语言:javascript
复制
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <script type="text/javascript" src="./jquery-1.12.4.js"></script>
  </head>
  <body>
    <form action="./recvfileswithcallback?callbackFnName=fileUploadSuccess"
        enctype="multipart/form-data" 
        method="POST" 
        target="formSubmitResult"
        >
      <input type="file" name="myfile"/>
      <input type="submit" value="submit"/>
      <input type="reset" value="reset"/>
    </form>
    
    <iframe name="formSubmitResult" style="width:750px; height:200px;"></iframe>
    
    <script>
      function fileUploadSuccess(params){
        alert("fileUploadSuccess called!\n"+params);
      }
    </script>
  </body>
</html>

代码示例:form_upload_callback_trigger.jsp

代码语言:javascript
复制
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
  </head>
  <body>
    <fieldset>
      <legend>submitted files info</legend>
      <pre>
${submittedFilesInfo}
      </pre>
    </fieldset>
    <script>
      parent.${callbackFnName}("${callbackParams}");
</script>
  </body>
</html>

代码示例:RecvFilesWithCallback.java

代码语言:javascript
复制
package webj2ee;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;

@MultipartConfig()
@WebServlet("/recvfileswithcallback")
public class RecvFilesWithCallback extends HttpServlet {
  private static final long serialVersionUID = 1L;

  public RecvFilesWithCallback() {
    super();
  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    doPost(request, response);
  }

  protected void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    request.setCharacterEncoding("UTF-8");
    
    // 解析上传文件
    StringBuilder submittedFilesInfo = new StringBuilder();
    Collection<Part> parts = request.getParts();
    Iterator<Part> itor = parts.iterator();
    while(itor.hasNext()) {
      Part part = itor.next();
      String name = part.getName();
      String submittedFileName = part.getSubmittedFileName();
      long size = part.getSize();
      String contentType = part.getContentType();
      submittedFilesInfo.append(name+": "+submittedFileName+", "+contentType+", "+size/1000 + "K" + "\r\n");
    }
    
    // 回调函数名、参数
    String callbackFnName = request.getParameter("callbackFnName");
    String callbackParams = "来自Servlet的返回值...";
    
    // 返回上传成功页面,触发上传成功回调函数
    request.setAttribute("submittedFilesInfo", submittedFilesInfo);
    request.setAttribute("callbackFnName", callbackFnName);
    request.setAttribute("callbackParams", callbackParams);
    request.getRequestDispatcher("./form_upload_callback_trigger.jsp").forward(request, response);
  }
}

效果展示:

3.4. 客户端发起 —— FileAPI + XMLHttpRequest 上传

这是功能最强大、最灵活的文件上传方案。

优点:功能强大、灵活、定制性强

老古董(IE7/8/9): × 传统浏览器环境中,不支持 Ajax 文件上传; 现代浏览器(>=IE10、Chrome、Firefox): a. 支持上传完成回调机制; b. 支持多选文件上传; c. 支持筛选上传文件类型; d. 支持限定上传文件尺寸; e. 支持文件上传进度监控;

缺点:只能在现代浏览器环境中使用;

3.4.1 File API

H5 提供了一组简洁有效的文件操作接口:File API

主要涉及:

FileList:用户通过file控件或拖拽选择的一组文件; File:FileList里面放的就是File; Blob:代表一段二进制数据,File就是继承自Blob; FileReader:用于从File、Blob中读取数据; FormData:用Ajax实现上传、进度显示时会用到;

特别注意:

H5 的 File API 虽然可以让我们访问本地文件系统,但只能被动地读取,也就是说只有用户主动触发了文件读取行为(比如通过file控件选择选择文件或拖拽文件),才能访问到File API;

浏览器兼容性:

3.4.2 XMLHttpRequest Level 2

特别注意,是 XMLHttpRequest Level 2,支持文件上传、上传进度展示等特性。与 H5 的 File API 相结合,可以发挥很大

浏览器兼容性:

例1:获取用户选择的文件(FileList、File)

核心逻辑:

代码示例:

代码语言:javascript
复制
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html><head>
  <script type="text/javascript" src="./jquery-1.12.4.js"></script>
</head><body>
  <form>
    <input id="myfile" type="file" multiple></input>
  </form>

  <script type="text/javascript">
    $("#myfile").bind("change", function(e){
      var fileList = this.files;
      console.dir(fileList);

      for(var i=0; i<fileList.length; i++){
        var file = fileList[i];
        console.log(file.name + ": " + file.size/1024 + "KB;");
      }
    });    
</script>
</body></html>

效果展示:

例2:获取用户拖拽的文件(FileList、File)

核心逻辑:

代码示例:

代码语言:javascript
复制
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <title>WebJ2EE FileAPI</title>
  <script type="text/javascript" src="./jquery-1.12.4.js"></script>
</head>
<body>
  <textarea id="myFileDrop" rows="10"></textarea>

  <script type="text/javascript">
    $("#myFileDrop").bind("drop", function(e) {
      // 阻止冒泡、阻止浏览器默认行为(浏览器默认会自动打开拖拽的文件)
      e.stopPropagation();
      e.preventDefault();

      // 通过 e.originalEvent.dataTransfer.files 获取拖拽进来的 FileList
      // (注: 经jquery包装的事件对象e中不包含dataTransfer对象,
      //      所以需要通过e.originalEvent访问浏览器的原始事件对象)
      var fileMsg = "";
      var fileList = e.originalEvent.dataTransfer.files;
      for(var i=0; i<fileList.length; i++){
        var file = fileList[i];
        fileMsg += file.name + ": " + file.size/1024 + "KB;\n";
      }

      // 将组装后的文件信息展示在textarea组件中
      $(this).val(fileMsg);
    });
</script>
</body>
</html>

效果展示:

例3:用 FileReader 预览图片

FileReader 是一种异步文件读取机制,用于读取File、Blob中的文件数据。

常用接口:

代码语言:javascript
复制
// 用于将 Blob 或 File 对象,按指定的编码(默认UTF-8),转化为字符串形式;
readAsText(Blob|File, opt_encoding):
// 用于将 Blob 或 File 对象,转换为一个基于base64编码的Data URL对象。(文件上传前预览就是靠这个技术)
readAsDataURL(Blob|File):

核心逻辑:

代码示例:

代码语言:javascript
复制
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <title>WebJ2EE FileAPI</title>
  <script type="text/javascript" src="./jquery-1.12.4.js"></script>
</head>
<body>
  
  <input id="myfile" type="file" accept="image/*" multiple></input>

  <div id="previewArea"></div>

  <script type="text/javascript">
    $("#myfile").bind("change", function(e){
      
      let fileList = this.files; // 获取用户选择的文件列表;
      
      for(let file of fileList ){ // 为每个文件,生成一张预览图;
        
        let reader = new FileReader(); // 构建一个FileReader实例;

        // FileReader是异步读取数据,注册onload事件,监听文件读取进度;
        reader.addEventListener("load", function () {

          // FileReader完成读取后,onload事件被触发,
          // 属性 result 中包含换后的 Data URL 对象;
          let imageDataUrl = reader.result; 

          // 构造一个Image结构
            let image = new Image();
              image.src = imageDataUrl;
              
              $("#previewArea").append( image );
        }, false);

            reader.readAsDataURL(file); // 将图片转换为基于Base64的Data URL对象;
      }
    });  
</script>
</body>
</html>

效果展示:

例4:用 onprogress 事件监听文件上传进度

XMLHttpRequest Level 2,支持 onprogress 事件,可以监听文件上传或下载中的进度。

核心逻辑:

代码示例:

代码语言:javascript
复制
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html><html><head>
  <meta charset="UTF-8"/>
  <script type="text/javascript" src="./jquery-1.12.4.js"></script>
  <style> *{ font-family:"Consolas"; } </style>
</head><body>
  <form>
    <div>select a file to upload.</div><br/>
    <div><input type="file" id="myfile" onchange="onFileSelected()"/></div><br/>
    <fieldset>
      <legend>FileInfo:</legend>
      fileName: <span id="fileName"></span><br/>
      fileSize: <span id="fileSize"></span><br/>
      fileType: <span id="fileType"></span><br/>
      progress: <span id="progress"></span><br/>
    </fieldset><br/>
    <input type="button" onclick="uploadFile()" value="Upload"/>
  </form>
  <script type="text/javascript">
    function onFileSelected() {
      // file的非multiple模式,FileList中只会有一个元素;
      var file = document.getElementById('myfile').files[0]; 
        
      // 展示fileName、fileSize、fileType
      $('#fileName').text(file.name);
      $('#fileSize').text(Math.round(file.size / 1024) + 'KB');
      $('#fileType').text(file.type);
      $('#progress').text("0%");
        }
      
      function uploadFile() {
        // 1. 获取用户选取的文件
        var file = document.getElementById('myfile').files[0];
        
        // 2. 用FormData组件要发送的表单数据(文件)
        var fd = new FormData();
        fd.append("myfile", file);
        
        // 3. 使用XMLHttpRequest发送请求 
        var xhr = new XMLHttpRequest();
        xhr.upload.addEventListener("progress", function(e) {
          let computable = e.lengthComputable; // 进度是否可计算
          if( computable ){
            let loaded = e.loaded; // 已上传量
            let total = e.total; // 数据总量
            $('#progress').text( Math.round(loaded / total * 100) + '%');
          }else {
            $('#progress').text( 'unable to compute progress!');
          }
        }, false);
        
        xhr.upload.addEventListener("load", function(e) { // 传输完成
          console.log("Transfer Complete!");
          }, false);
        
        xhr.addEventListener("load", function(e) { // 请求完成
          console.log(xhr.responseText);
          }, false);
        
        xhr.open("POST", "./apacherecvfile");
        xhr.send(fd);
      }
</script>
</body></html>

效果展示:

例5:FormData + XMLHttpRequest 上传文件

核心逻辑:

代码示例:

代码语言:javascript
复制
<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"/>
    <script type="text/javascript" src="./jquery-1.12.4.js"></script>
    <style> *{ font-family:"Consolas"; }</style>
  </head>
  <body>
    <form>
      <div>select a file to upload.</div><br/>
      
      <div>
        <input type="file" 
          id="myfile" 
          multiple="multiple" 
          accept="image/*" 
          onchange="onFileSelected()"
          />
      </div><br/>
      
      <fieldset>
        <legend>selected files info</legend>
        <pre id="selectedFilesInfo">
        </pre>
      </fieldset><br/>
      
      <input type="button" onclick="uploadFile()" value="Upload"/>
    </form>
    <script type="text/javascript">
      function onFileSelected() {
        var files = document.getElementById('myfile').files; 
          
        var selectedFilesInfo = "";
        for(var i=0; i<files.length; i++){
          var file = files[i];
          var name = file.name;
          var contentType = file.type;
          var size = file.size;
          selectedFilesInfo += name+": "+contentType+", "+size/1000 + "K" + "\n";
        }
        
        $("#selectedFilesInfo").text(selectedFilesInfo);
          }
        
        function uploadFile() {
          // 1. 获取用户选取的文件
          var files = document.getElementById('myfile').files; 
          
          // 2. 用 FormData 组装 Multipart 型请求
          var fd = new FormData();
          for(var i=0; i<files.length; i++){
            fd.append("myfile", files[i], files[i].name);
          }
          
          // 3. 使用XMLHttpRequest Level 2发送请求 
          // 注:推荐使用 Fetch API 替代 XMLHttpRequest
          var xhr = new XMLHttpRequest();
          xhr.addEventListener("load", function(e) { // 请求完成
            alert("上传完成!");
            }, false);
          
          xhr.open("POST", "./HandleUpload");
          xhr.send(fd);
        }
</script>
  </body>
</html>

效果展示:

参考:

RFC 1867: https://tools.ietf.org/html/rfc1867 RFC 6532: https://tools.ietf.org/html/rfc6532 https://caniuse.com/ https://developer.mozilla.org/zh-CN/ 《Servlet、JSP 和 Spring MVC 初学指南》 《Spring 入门经典》


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebJ2EE 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档