Прочитайте загрузку и загрузку файлов в SpringMVC в одной статье.

задняя часть браузер Firefox jQuery

За последние два дня я изучал загрузку и загрузку файлов в SpringMVC и столкнулся с некоторыми ямами.Вот краткое изложение.

1. Принцип загрузки и скачивания файлов

Загрузка и скачивание файлов в Интернете неотделимы от протокола HTTP.Чтобы глубже понять загрузку и скачивание файлов, необходимо иметь полное представление о протоколе HTTP.

1.1 Загрузка файла

В TCP/IP самым ранним механизмом загрузки файлов был FTP, стандартный механизм отправки файлов от клиента к серверу. В веб-разработке используется протокол прикладного уровня HTTP, а тип содержимого передачи устанавливается в заголовке запроса.multipart/form-data; boundary=流分隔符值Для загрузки файлов этот разделитель потоков используется для различения начала и конца загрузки файла.Ниже приведены заголовки и параметры сообщений, которые я перехватил в браузере Firefox при загрузке нескольких файлов.

文件上传消息头.jpg
文件上传参数.jpg

Соответственно, в HTML установите атрибут Method = "post" enctype="`multipart/form-data" для элемента формы, установите type = "file" для элемента ввода и установите атрибут "multiple", если несколько файлов загружен. Пример кода выглядит следующим образом.

<form action="" method="post" enctype="multipart/form-data" onsubmit="return check()">
    <input type="file" name="file" id="file" multiple="multiple"><br>
    <input type="submit" value="上传">
</form>

Подробное описание атрибута enctype в виде:

  • application/x-www=form-urlencoded: по умолчанию обрабатывается только значение атрибута value в поле формы.Формы, использующие этот метод кодирования, будут обрабатывать значение в поле формы в кодировке URL.
  • multipart/form-data: Этот метод кодирования будет обрабатывать данные формы в бинарном потоке.Этот метод кодирования также будет инкапсулировать содержимое файла, указанного в поле файла, в параметры запроса, и не будет кодировать символы.
  • text/plain: За исключением преобразования пробелов в знаки "+", остальные символы не кодируются. Этот метод подходит для отправки писем напрямую через форму.

1.2 Загрузка файла

Установив Content-Disposition и Content-Type в заголовке ответного сообщения, браузер не может использовать определенный способ или активировать программу для обработки файлов типа MIME, чтобы браузер запрашивал сохранение файла, то есть загрузку файл. Значение Content-Disposition равноattachment;filename=文件名, значение Content-Type равноapplication/octet-streamилиapplication/x-msdownload. Китайцам в файле стоит обратить внимание на проблему с кодировкой, а между разными браузерами есть отличия.

文件下载.jpg

2. Загрузка и загрузка файлов в SpringMVC

Весь код, задействованный в этой статье, можно найти на моем GitHub,портал.

2.1 Загрузка файла

При загрузке файла лучше всего проверить переднюю и заднюю часть, например размер файла, тип файла и т. д. Здесь я выполняю только внутреннюю проверку.

Код страницы загрузки файла:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>文件上传与下载</title>
    <noscript>
        <style>
            #main {
                display: none !important;
            }
        </style>
        <p align="center">您的浏览器禁止了JS,请先启动脚本</p>
    </noscript>
    <script>
        function check() {
            var file = document.getElementById("file");
            if (file.value == "") {
                alert("上传的文件为空")
                return false;
            }
            return true;
        }
    </script>
</head>
<body>
    <div id="main" style="width:500px; margin: 0 auto;">
        <span style="color:red;">${msg}</span>
        <form action="" method="post" enctype="multipart/form-data" onsubmit="return check()">
            <input type="file" name="file" id="file" multiple="multiple"><br>
            <input type="submit" value="上传">
        </form>
    </div>
</body>
</html>

Делая ограничения, вы не должны быть прямо ограничены в SpringMVC, особенно большие файлы (2M и более), иначе Tomcat отключит поток приема при загрузке, и браузер потеряет отклик. Это место беспокоит меня, эта ошибка и Springmvc не имеют ничего общего, и есть связь с Tomcat. Пожалуйста, посмотрите на картинку ниже. Некоторые люди говорят, что у Tomcat7 нет этой проблемы, но это не рекомендуется для решения проблемы. проблема.

Tomcat文件上传大小限制.jpg

После некоторых исследований мое решение было использовать перехватчик для ограничения размера загрузочных файлов. Когда перехватчик перехватывает файл, превысив заданное значение, выдается исключение, и исключение обрабатывается в контроллере. Здесь время разбора исключения следует задерживаться в конфигурации. В конфигурации перехватчика ограничивайте свойства перехвата, получите это значение конфигурации в перехватчике, и не напрямую пишу его в перехватчике. Это исключение попадает в контроллер, что указывает на то, что загруженный файл превышает предел.

Конфигурация в SpringMVC:

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--设置请求编码-->
    <property name="defaultEncoding" value="UTF-8"/>
    <property name="uploadTempDir" value="WEB-INF/tmp"/>
    <!--设置允许单个上传文件的最大值,不要在这里配置-->
    <!--<property name="maxUploadSizePerFile" value="31457280"/>-->
    <!--延迟解析,在Controller中抛出异常-->
    <property name="resolveLazily" value="true"/>
</bean>
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/*upload*"/>
        <bean class="com.wenshixin.interceptor.FileUploadInterceptor">
            <property name="maxSize" value="31457280"/>
        </bean>
    </mvc:interceptor>
</mvc:interceptors>

Перехватчик:

public class FileUploadInterceptor implements HandlerInterceptor {
    private long maxSize;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        if (httpServletRequest != null && ServletFileUpload.isMultipartContent(httpServletRequest)) {
            ServletRequestContext servletRequestContext = new ServletRequestContext(httpServletRequest);
            long requestSize = servletRequestContext.contentLength();
            if (requestSize > maxSize) {
                // 抛出异常
                throw new MaxUploadSizeExceededException(maxSize);
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }

    public void setMaxSize(long maxSize) {
        this.maxSize = maxSize;
    }
}

Метод обработки исключений в контроллере:

@ExceptionHandler(MaxUploadSizeExceededException.class)
public String handException(MaxUploadSizeExceededException e, HttpServletRequest request) {
    request.setAttribute("msg", "文件超过了指定大小,上传失败!");
    return "fileupload";
}

SpringMVC использует объект MultipartFile для получения загруженного файла, через этот объект можно получить имя файла и тип файла, а файл записывается на диск с помощью метода transferTo(). При загрузке файла переименуйте файл, чтобы предотвратить перезапись загруженного файла с тем же именем.Здесь я использую значение UUID + имя файла, разделенные символом подчеркивания.

Метод загрузки файла в контроллере:

@PostMapping(value = "/fileupload")
public String fileUpload(@RequestParam(value = "file") List<MultipartFile> files, HttpServletRequest request) {
    String msg = "";
    // 判断文件是否上传
    if (!files.isEmpty()) {
        // 设置上传文件的保存目录
        String basePath = request.getServletContext().getRealPath("/upload/");
        // 判断文件目录是否存在
        File uploadFile = new File(basePath);
        if (!uploadFile.exists()) {
            uploadFile.mkdirs();
        }
        for (MultipartFile file : files) {
            String originalFilename = file.getOriginalFilename();
            if (originalFilename != null && !originalFilename.equals("")) {
                try {
                    // 对文件名做加UUID值处理
                    originalFilename = UUID.randomUUID() + "_" + originalFilename;
                    file.transferTo(new File(basePath + originalFilename));
                } catch (IOException e) {
                    e.printStackTrace();
                    msg = "文件上传失败!";
                }
            } else {
                msg = "上传的文件为空!";
            }
        }
        msg = "文件上传成功!";
    } else {
        msg = "没有文件被上传!";
    }
    request.setAttribute("msg", msg);
    return "fileupload";
}

Рендеринг загрузки файла:

文件下载效果图.gif

2.2 Загрузка файла

Для страницы загрузки я использовал Jquery для динамического создания списка загрузки для предварительного кодирования URL-адреса, чтобы предотвратить вмешательство специальных символов, таких как # в имени файла, и удалить значение UUID отображаемого имени файла, а также для Браузеры IE со специальной китайской обработкой.

Страница загрузки:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>文件上传与下载</title>
    <script src="${pageContext.request.contextPath}/js/jquery-1.12.4.min.js"></script>
    <script>
        $(function(){
            var targer = $("#main")
            $.ajax({
                url: "fileList",
                dataType: "json",
                success: function (data) {
                    data = JSON.parse(data)
                    for (var i in data) {
                        var a = $("<a></a><br>").text(data[i].substring(data[i].indexOf("_")+1))
                        a.attr("href", "${pageContext.request.contextPath}/download?filename="+encodeURIComponent(data[i]))
                        targer.append(a)
                    }
                }
            })
        })
    </script>
</head>
<body>
    <div id="main" style="width:500px; margin: 0 auto;">
    </div>
</body>
</html>

Метод загрузки в контроллере:

@RequestMapping(value = "/download")
public ResponseEntity<byte[]> fileDownload(String filename, HttpServletRequest request) throws IOException {
    String path = request.getServletContext().getRealPath("/upload/");
    File file = new File(path + filename);
    //        System.out.println("转码前" + filename);
    filename = this.getFilename(request, filename);
    //        System.out.println("转码后" + filename);
    // 设置响应头通知浏览器下载
    HttpHeaders headers = new HttpHeaders();
    // 将对文件做的特殊处理还原
    filename = filename.substring(filename.indexOf("_") + 1);
    headers.setContentDispositionFormData("attachment", filename);
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
    return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file), headers, HttpStatus.OK);
}

// 根据不同的浏览器进行编码设置,返回编码后的文件名
public String getFilename(HttpServletRequest request, String filename) throws UnsupportedEncodingException {
    String[] IEBrowerKeyWords = {"MSIE", "Trident", "Edge"};
    String userAgent = request.getHeader("User-Agent");
    for (String keyword : IEBrowerKeyWords) {
        if (userAgent.contains(keyword)) {
            return URLEncoder.encode(filename, "UTF-8");
        }
    }
    return new String(filename.getBytes("UTF-8"), "ISO-8859-1");
}

Отрисовка загруженного файла (браузеры Google, Firefox, IE, 360):

文件上传效果图.gif

Загрузка и скачивание файлов - очень распространенная функция в веб-разработке, но сделать это хорошо не так просто.Следует учитывать совместимость браузера.Если вы стремитесь к пользовательскому опыту, вы также можете отображать индикатор выполнения при загрузке файлов и AJAX реализует страницу без обновления загрузки, я глубоко чувствую, что мой уровень интерфейса все еще нуждается в улучшении😭, не говоря уже об обучении.

Добро пожаловать в общедоступную учетную запись WeChat ниже, там есть различные учебные материалы, которыми можно поделиться бесплатно!

编程心路