Spring mvc文件上传下载


简介

文件上传是项目开发中最常见的功能。为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器。
一旦设置了enctype为multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。在2003年,Apache Software Foundation发布了开源的Commons FileUpload组件,其很快成为Servlet/JSP程序员上传文件的最佳选择。
Servlet3.0规范已经提供方法来处理文件上传,但这种上传需要在Servlet中完成。而Spring MVC则提供了更简单的封装。
Spring MVC为文件上传提供了直接的支持,这种支持是用即插即用的MultipartResolver实现的。Spring MVC使用Apache Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver。因此,SpringMVC的文件上传还需要依赖Apache Commons FileUpload的组件。

核心代码

package com.iogogogo.api;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Base64Utils;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.unit.DataSize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

/**
 * # 禁用 thymeleaf 缓存
 * spring.thymeleaf.cache=false
 * <p>
 * # 是否支持批量上传   (默认值 true)
 * spring.servlet.multipart.enabled=true
 * <p>
 * # 上传文件的临时目录 (一般情况下不用特意修改)
 * spring.servlet.multipart.location=
 * <p>
 * # 上传文件最大为 1M (默认值 1M 根据自身业务自行控制即可)
 * spring.servlet.multipart.max-file-size=1048576
 * <p>
 * # 上传请求最大为 10M(默认值10M 根据自身业务自行控制即可)
 * spring.servlet.multipart.max-request-size=10485760
 * <p>
 * # 文件大小阈值,当大于这个阈值时将写入到磁盘,否则存在内存中,(默认值0 一般情况下不用特意修改)
 * spring.servlet.multipart.file-size-threshold=0
 * <p>
 * # 判断是否要延迟解析文件(相当于懒加载,一般情况下不用特意修改)
 * spring.servlet.multipart.resolve-lazily=false
 * <p>
 * Created by tao.zeng on 2018/12/6.
 * http://localhost:8080/api/file/download2stream
 * http://localhost:8080/api/file/download
 */
@RestController
@RequestMapping("/api/file")
public class FileApi {

    private Logger logger = LoggerFactory.getLogger(FileApi.class);

    private final static String FILE_NAME = "main.csv";

    private final static String FILE_CONTENT = "key,value";

    private final static String FILE_PATH = System.getProperty("user.dir") + File.separator + "tmp";

    static {
        File file = new File(FILE_PATH);
        if (!file.exists()) {
            file.mkdirs();
        }
    }


    /**
     * 通过 OutputStream 每次往客户端写 buffer
     *
     * @param response
     */
    @GetMapping("/download")
    public void download(HttpServletResponse response) {
        InputStream inStream = new ByteArrayInputStream(FILE_CONTENT.getBytes());
        // 设置强制下载不打开
        response.setContentType("application/force-download");
        // 设置content-type
        response.setHeader(HttpHeaders.CONTENT_TYPE, "application/octet-stream");
        // 设置文件名
        response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;fileName=" + FILE_NAME);
        byte[] buffer = new byte[1024];
        ServletOutputStream outStream = null;
        BufferedInputStream bufferStream = null;
        try {
            outStream = response.getOutputStream();
            bufferStream = new BufferedInputStream(inStream);
            int len;
            while ((len = bufferStream.read(buffer)) != -1) {
                outStream.write(buffer, 0, len);
            }
            outStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(outStream, bufferStream);
        }
    }

    /**
     * 流式下载
     *
     * @return
     */
    @GetMapping("/download2stream")
    public ResponseEntity<Resource> download2stream() {
        InputStream inStream = null;
        try {
            inStream = new ByteArrayInputStream(FILE_CONTENT.getBytes());
            Resource resource = new InputStreamResource(inStream);
            return ResponseEntity.ok()
                    .contentType(MediaType.parseMediaType("application/octet-stream;charset=UTF-8"))
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;fileName=" + FILE_NAME)
                    .body(resource);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("下载异常:" + e);
        } finally {
            close(inStream);
        }

    }

    /**
     * multipart/form-data
     *
     * @param file
     * @return
     */
    @PostMapping("/upload")
    public Map<String, Object> upload(@RequestParam("file") MultipartFile file) {
        // 获取 resources 目录
        // String realPath = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getServletContext().getRealPath("");
        Map<String, Object> result = new HashMap<>(16);
        try {
            logger.info("[文件类型] - [{}]", file.getContentType());
            logger.info("[文件名称] - [{}]", file.getOriginalFilename());
            logger.info("[文件大小] - [{}]", file.getSize());
            byte[] bytes = file.getBytes();
            String fileName = FILE_PATH + File.separator + file.getOriginalFilename();
            Path path = Paths.get(fileName);
            Files.write(path, bytes);

            result.put("contentType", file.getContentType());
            result.put("fileName", file.getOriginalFilename());
            result.put("fileSize", file.getSize());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 多文件上传
     *
     * @param files
     * @return
     */
    @PostMapping("/multi-file-upload")
    public List<Map<String, Object>> multiFileUpload(@RequestParam("file") MultipartFile[] files) {
        if (files == null || files.length == 0) {
            return null;
        }
        List<Map<String, Object>> results = new ArrayList<>();
        Map<String, Object> map;
        try {
            for (MultipartFile file : files) {
                map = new HashMap<>(16);
                String fileName = FILE_PATH + File.separator + file.getOriginalFilename();
                file.transferTo(new File(fileName));
                map.put("contentType", file.getContentType());
                map.put("fileName", file.getOriginalFilename());
                map.put("fileSize", file.getSize());
                results.add(map);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return results;
    }


    /**
     * Base64文件上传
     * <p>
     * base64编码: http://base64.xpcha.com/pic.html
     *
     * @param base64
     * @return
     */
    @PostMapping("/base64-upload")
    public Map<String, Object> uploadBase64(String base64) {
        Map<String, Object> map = new HashMap<>(16);
        // 防止有的传了 data:image/png;base64, 有的没传的情况
        String[] d = base64.split("base64,");
        final byte[] bytes = Base64Utils.decodeFromString(d.length > 1 ? d[1] : d[0]);
        // BASE64 方式的 格式和名字需要自己控制(如 png 图片编码后前缀就会是 data:image/png;base64,)
        File tempFile = new File(FILE_PATH + File.separator + UUID.randomUUID() + ".jpg");
        try {
            FileCopyUtils.copy(bytes, tempFile);
            map.put("fileSize", tempFile.length());
            map.put("fileName", tempFile.getName());
            map.put("parent", tempFile.getParent());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return map;
    }

    private void close(Closeable... closeables) {
        try {
            if (closeables != null) {
                for (Closeable io : closeables) {
                    if (io != null) {
                        io.close();
                    }
                }
            }
        } catch (IOException e) {
            logger.warn("io close exception {}", e);
        }
    }

    @Bean
    public MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        // 最大支持文件大小
        factory.setMaxFileSize(DataSize.ofMegabytes(10));

        // 最大支持请求大小
        factory.setMaxRequestSize(DataSize.ofMegabytes(100));

        // 支持文件写入磁盘.
        // factory.setFileSizeThreshold();

        // Sets the directory location where files will be stored.
        // factory.setLocation("上传文件的临时目录");
        return factory.createMultipartConfig();
    }
}

文章作者: 阿牛
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 阿牛 !
评论
  目录