Spring MVC 使用介绍(十四)文件上传下载

一、概述

文件上传时,http请求头Content-Type须为multipart/form-data,有两种实现方式:

1、基于FormData对象,该方式简单灵活

2、基于<form>表单元素,method设为POST,enctype设置为multipart/form-data,在form表单上提交

web容器收到该请求时,须根据请求头将字节流解析为文件对象,spring mvc 提供了MultipartResolver、MultipartFile两个接口用于支持文件上传功能

二、MultipartResolver & MultipartFile

1、MultipartResolver接口提供了文件解析功能,其定义如下:

public interface MultipartResolver {
    boolean isMultipart(HttpServletRequest request);
    MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
    void cleanupMultipart(MultipartHttpServletRequest request);
}

Spring MVC使用Apache Commons fileupload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver

2、MultipartFile接口代表上传的文件,提供了文件操作的相关功能,其定义如下:

public interface MultipartFile {
    String getName();
    String getOriginalFilename(); // 原文件名
    String getContentType();
    boolean isEmpty();
    long getSize();
    byte[] getBytes() throws IOException;
    InputStream getInputStream() throws IOException; // 获取文件流
    void transferTo(File dest) throws IOException, IllegalStateException; // 保存文件
}

三、使用示例

1、添加pom依赖

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

2、spring-mvc.xml中配置MultipartResolver

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 上传文件大小限制,单位为字节-10Mb -->
    <property name="maxUploadSize">
        <value>10485760</value>
    </property>
    <!-- 请求的编码格式 -->
    <property name="defaultEncoding">
        <value>UTF-8</value>
    </property>
</bean>

3、controller

@Controller
public class FileController {
    /**
     * 文件存储目录
     */
    private static final String uploadFolder = "d:/upload-file/";

    /**
     * 上传
     */
    @ResponseBody
    @RequestMapping(value = "/upload", method = RequestMethod.POST)
    public Map<String, String> fileUpload(@RequestParam("file") MultipartFile file, @RequestParam("fileType") String fileType) throws Exception {
        
        // 创建文件目录
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
        String dateStr = sdf.format(new Date());
        String dirPath = uploadFolder + dateStr;
        File dir = new File(dirPath);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        
        // 保存文件
        String fileName = UUID.randomUUID() + this.getFileNameSuffix(file.getOriginalFilename());
        File savedFile = new File(dir + "/" + fileName);
        file.transferTo(savedFile);
        
        Map<String, String> result = new HashMap<String, String>();
        result.put("fileName", file.getOriginalFilename());
        result.put("fileType", fileType);
        result.put("filePath", dateStr + "/" + fileName);
        
        return result;
    }
    
    /**
     * 下载
     */
    @RequestMapping("/download")
    public void downloadFile(@RequestParam(value = "fileName", required = false) String fileName, @RequestParam("filePath") String filePath, HttpServletResponse response) throws Exception {
        File file = new File(uploadFolder + filePath);
        if (!file.exists()) {
            return;
        }
        
        // 读取字节流到缓存
        InputStream in = new BufferedInputStream(new FileInputStream(file));
        byte[] buffer = new byte[in.available()];
        in.read(buffer);
        in.close();
        
        // 设置ContentType
        String suff = this.getFileNameSuffix(file.getName());
        if (suff != null) {
            suff = suff.toLowerCase();
        }
        switch (suff) {
            case ".jpg":
            case ".jpeg":
                response.setContentType(MediaType.IMAGE_JPEG_VALUE);
                break;
            case ".png":
                response.setContentType(MediaType.IMAGE_PNG_VALUE);
                break;
            case ".gif":
                response.setContentType(MediaType.IMAGE_GIF_VALUE);
                break;
            default:
                response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
                break;
        }
        
        // 输出
        response.addHeader("Content-Disposition", "attachment;filename="" + (StringUtils.isEmpty(fileName) ? file.getName() : fileName));
        response.addHeader("Content-Length", "" + file.length());
        response.setContentType("application/x-msdownload");
        OutputStream out = new BufferedOutputStream(response.getOutputStream());
        out.write(buffer);
        out.flush();
    }
    
    /**
     * 获取文件名后缀*/
    private String getFileNameSuffix(String fileName) {
        
        if (StringUtils.isEmpty(fileName)) {
            return null;
        }
        
        String suffix;
        int index = fileName.lastIndexOf(".");
        if (index == -1) {
            suffix = null;
        }
        else {
            suffix = fileName.substring(index);
        }
        
        return suffix;
    }
}

4、前端页面(test.html)

<!DOCTYPE html>
<html>
<head>
    <title>test page</title>
</head>
<body>
    <div>
        <input id="upload" type="file" onchange="uploadFile(event)" />
    </div>
    <div>
        <input id="download" type="button" onclick="downloadFile()" value="下载" />
    </div>
</body>
<script>
    var context = '/test'

    function uploadFile(event) {
        // 文件类型过滤
        var file = event.currentTarget.files[0];
        var fileName = file.name;
        if (/.(jpg|jpeg|png|gif)$/i.test(fileName) == false) {
            alert('提示', '不支持的图片类型,头像只支持.jpg,.jpeg,.png,.gif');
            return;
        }

        var formData = new FormData();
        formData.append('fileType', 'portrait');
        formData.append('file', event.currentTarget.files[0]);

        // 发送请求
        var xhr = new XMLHttpRequest();
        xhr.open('POST', context + '/upload', true);
        xhr.onreadystatechange = function() {
            if (xhr.readyState==4 && xhr.status==200) {
                document.getElementById('download').fileData = JSON.parse(xhr.responseText);
                alert("上传成功");
            }
        }
        xhr.send(formData);

        // 重置文件上传控件,使得重复选择同一个文件时,onchange依旧触发
        event.target.value = null;
    }

    function downloadFile() {
        var fileData = document.getElementById('download').fileData;
        location.href = context + '/download?filePath=' + decodeURIComponent(fileData.filePath) + '&fileName=' + decodeURIComponent(fileData.fileName);
    }
</script>
</html>

访问http://file-test/test.html,可测试文件的上传下载功能

补充:示例使用@ResponseBody输出json数据,因此还需添加相关配置,详细可参考Spring MVC 使用介绍(五)—— 注解式控制器(一):基本介绍

另外,文件下载除了示例中文件流方式,还可以基于spring对静态文件的支持功能,详细可参考Spring MVC 使用介绍(十一)—— 跨域与静态资源访问

参考:

Spring MVC的文件上传

原文地址:https://www.cnblogs.com/MattCheng/p/10374203.html