专栏首页WebJ2EEWEB:文件上传 —— 看这篇就够了

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

文件上传知识体系

如下图

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" 编码形式,用于支持文件传输;
<%@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 大法好,无需插件,就能处理上传的文件。

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);
        }
    }
}
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 则是最好的选择。

关键依赖:

<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>

代码示例:

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);
            }
        }
    }
}
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 示例

关键依赖:

<!-- 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

<?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

<?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

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

<?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

<?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 端请求转发;

关键依赖:

<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>

代码示例:

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 问题的说明

代码示例:

<%@ 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

<%@ 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

<%@ 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

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)

核心逻辑:

代码示例:

<%@ 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)

核心逻辑:

代码示例:

<%@ 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中的文件数据。

常用接口:

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

核心逻辑:

代码示例:

<%@ 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 事件,可以监听文件上传或下载中的进度。

核心逻辑:

代码示例:

<%@ 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 上传文件

核心逻辑:

代码示例:

<%@ 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 入门经典》


本文分享自微信公众号 - WebJ2EE(WebJ2EE),作者:WEBJ2EE

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • NPM那些库(2):cross-env、fs-extra、svgo、parse5、path

    “NPM那些库” 是系列文章,每篇介绍几个 Node 环境中常用的库,目的是:总结、记录、分享。

    WEBJ2EE
  • 【前端】闭包(closure)

    计算机科学中,闭包(Closure)是引用了自由变量的函数。即使自由变量原来所属的内存空间不存在了,该自由变量也依然对该函数有效。闭包是函数和其相关的“环境”组...

    WEBJ2EE
  • Spring周边:日志——中

    门面设计模式是面面向对象设计模式中的一种,日志框架采用的就是这种模式,类似JDBC的设计理念。它只提供一套接口规范,自身不负责日志功能的实现,目的是让使用者不需...

    WEBJ2EE
  • 一起来学 SpringBoot 2.x | 第十七篇:轻松搞定文件上传

    文件上传和下载是JAVA WEB中常见的一种操作,文件上传主要是将文件通过IO流传输到服务器的某一个特定的文件夹下;刚开始工作那会一个上传文件常常花费小半天的时...

    芋道源码
  • 一起来学SpringBoot | 第十七篇:轻松搞定文件上传

    文件上传和下载是 JAVA WEB中常见的一种操作,文件上传主要是 将文件通过IO流传输到服务器的某一个特定的文件夹下;刚开始工作那会一个上传文件常常花费小半天...

    battcn
  • 《SpringMVC从入门到放肆》十五、SpringMVC之上传文件

    上一篇我们学习了数据分组校验,已经可以灵活的在项目中进行数据校验了,今天来学习SpringMVC的上传文件功能。相对来说SpringMVC的上传功能,还是比较简...

    I Tech You_我教你
  • jface databinding:部分实现POJO对象的监测

    版权声明:本文为博主原创文章,转载请注明源地址。 https://blog.csdn.net...

    用户1148648
  • 从SAP最佳业务实践看企业管理(45)-SD-退货和投诉

    SD 111退货和投诉 用途: 该业务情景描述销售订单退货处理。该过程开始于参考货物原始发票的退货销售订单。已打,印 RMA 凭证,且将其转发到客户,以便将其附...

    SAP最佳业务实践
  • Spring Boot整合Scheduled定时任务器、整合Quartz定时任务框架

    首先说明一下,这里使用的是Springboot2.2.6.RELEASE版本,由于Springboot迭代很快,所以要注意版本问题。

    别先生
  • 【行业】网购退货率超三成 用户数据分析成电商出路

    据国外媒体报道,虽然免费送货与宽松的退货政策提振了电子商务行业,但退货问题也让商家头疼。现在,渠道开始研究自己的订单数据,让购买者留住自己拍下的商...

    机器学习AI算法工程

扫码关注云+社区

领取腾讯云代金券