首页 > 其他分享 >从字节到文件下载:揭秘 Spring 中 MultipartFile 的转换与浏览器端自动下载实现

从字节到文件下载:揭秘 Spring 中 MultipartFile 的转换与浏览器端自动下载实现

时间:2024-08-12 13:28:50浏览次数:8  
标签:文件 return String Spring filename MultipartFile public 下载

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

大家在日常浏览器点击下载操作的时候,有没有考虑过这个下载是怎么实现的?为什么当我们点击对应的下载按钮或者下载链接的时候,浏览器能够下载对应的文件?今天就来深度进行剖析解读一下。


一、下载功能核心Java代码

    public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException {
        // 设置 header 和 contentType
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        // 输出附件
        IoUtil.write(response.getOutputStream(), false, content);
        //将传入的字节数组转换为MultipartFile
        MultipartFile multipartFile = new ByteMultipartFile(content, filename, MediaType.APPLICATION_OCTET_STREAM_VALUE);
        String fileName = FileUploadUtils.uploadMinio(multipartFile);
    }

二、代码解析

1.设置 Content-Disposition 响应头

代码如下(示例):


response.setHeader("Content-Disposition",
 "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));

这行代码设置了 Content-Disposition 响应头,其值为 “attachment”,这告诉浏览器这个响应的内容应该被视为一个附件,而不是在浏览器内直接显示的内容。filename 参数指定了下载文件的名称。使用 URLEncoder.encode 是为了确保文件名中的特殊字符被正确编码,以便浏览器可以正确处理。

2.设置 Content-Type 响应头

代码如下(示例):


response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);

这行代码设置了 Content-Type 响应头,其值为 “application/octet-stream”,这是一个通用的二进制文件类型,表示返回的数据是二进制流。当浏览器接收到这种类型的响应时,它会询问用户是否要保存文件,而不是尝试显示它。

3.写入文件内容到响应输出流


IoUtil.write(response.getOutputStream(), false, content);

这行代码将字节数组 content 写入到 HTTP 响应的输出流中。IoUtil.write 方法(假设这是某个实用工具类的一部分)负责将字节数组写入到输出流。一旦这些字节被写入,它们就会被发送到客户端。

4.文件写入到本地磁盘

    writeFileToDisk("C:\\Users\\20968\\Desktop\\risk-back", filename, content);
        //写入到本地磁盘
   public static void writeFileToDisk(String filePath, String filename, byte[] content) throws IOException {
        // 确定文件存放路径
        File file = new File(filePath, filename);
        // 确保目录存在
        File directory = file.getParentFile();
        if (!directory.exists()) {
            directory.mkdirs();
        }

        // 写入文件
        try (FileOutputStream fos = new FileOutputStream(file)) {
            IoUtil.write(fos, false, content);
        }
    }

5.文件写入到Mnio中

5.1 主方法

//将传入的字节数组转换为MultipartFile
MultipartFile multipartFile = new ByteMultipartFile(content, filename, MediaType.APPLICATION_OCTET_STREAM_VALUE);
String fileName = FileUploadUtils.uploadMinio(multipartFile);

5.2 实现MultipartFile 接口

ByteMultipartFile 类通过实现 MultipartFile 接口,重新实现了接口中定义的方法,以满足特定的需求,即从字节数组创建一个 MultipartFile 对象,因为Minio的api接口中需要的是MultipartFile 对象,

public class ByteMultipartFile implements MultipartFile {

    private final byte[] bytes;
    private final String name;
    private final String contentType;

    public ByteMultipartFile(byte[] bytes, String name, String contentType) {
        this.bytes = bytes;
        this.name = name;
        this.contentType = contentType;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String getOriginalFilename() {
        return name;
    }

    @Override
    public String getContentType() {
        return contentType;
    }

    @Override
    public boolean isEmpty() {
        return bytes == null || bytes.length == 0;
    }

    @Override
    public long getSize() {
        return bytes.length;
    }

    @Override
    public byte[] getBytes() throws IOException {
        return bytes;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return new ByteArrayInputStream(bytes);
    }

    @Override
    public void transferTo(File dest) throws IOException, IllegalStateException {
        // Implement if needed
    }
}

5.3文件上传工具类

// 删减版本

/**
 * 文件上传工具类
 *
 * @author ruoyi
 */
public class FileUploadUtils {
    /**
     * 默认大小 50M
     */
    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;

    /**
     * 默认的文件名最大长度 100
     */
    public static final int DEFAULT_FILE_NAME_LENGTH = 100;

    /**
     * 默认上传的地址
     */
    private static String defaultBaseDir = RuoYiConfig.getProfile();

    public static void setDefaultBaseDir(String defaultBaseDir) {
        FileUploadUtils.defaultBaseDir = defaultBaseDir;
    }

    public static String getDefaultBaseDir() {
        return defaultBaseDir;
    }

    /**
     * Minio默认上传的地址
     */
    private static String bucketName = MinioConfig.getBucketName();

    public static String getBucketName() {
        return bucketName;
    }

    @Value("${minio.url}")
    private String URL;

    @Value("${minio.bucketName}")
    private String BUCKETNAME;

    @Value("${minio.accessKey}")
    private String ACCESSKEY;
    
    @Value("${minio.secretKey}")
    private String SECRETKEY;
    /**
     * 以默认BucketName配置上传到Minio服务器
     *
     * @param file 上传的文件
     * @return 文件名称
     * @throws Exception
     */
    public static final String uploadMinio(MultipartFile file) throws IOException {
        try {
            return uploadMinino(getBucketName(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
        } catch (Exception e) {
            throw new IOException(e.getMessage(), e);
        }
    }
}

总结

当浏览器接收到带有这些特定头的响应时,它会识别出这是一个文件下载请求,并自动弹出一个对话框让用户选择保存文件的位置。这就是为什么即使方法返回值是 void,前端仍然可以下载文件的原因。实际上,文件的传输是在 HTTP 响应体中完成的,而不是通过方法的返回值。

至于 MultipartFile 的创建部分,这看起来像是在服务器端处理文件上传的逻辑,可能是为了将上传的文件存储到 MinIO 或其他存储服务中。这部分代码与前端下载逻辑无关,它只是将字节数组转换为一个 MultipartFile 对象,然后可能用于进一步的处理,例如上传到服务器或云存储。

标签:文件,return,String,Spring,filename,MultipartFile,public,下载
From: https://blog.csdn.net/weixin_51480428/article/details/141125426

相关文章

  • 在 Spring Boot 中使用工厂模式实现灵活的通知服务
    在现代软件开发中,特别是在构建可扩展和易于维护的应用程序时,设计模式扮演着至关重要的角色。其中之一是工厂模式,它允许我们在运行时根据特定条件创建对象,而无需硬编码具体的类名。本篇文章将通过一个具体例子来展示如何在SpringBoot项目中使用工厂模式来实现一个灵活的通知服......
  • springboot社区食堂管理平台-计算机毕业设计源码86404
    目 录1绪论1.1研究背景1.2研究意义1.3论文结构与章节安排2 社区食堂管理平台系统分析2.1可行性分析2.2系统流程分析2.2.1 数据流程3.3.2 业务流程2.3 系统功能分析2.3.1功能性分析2.3.2非功能性分析2.4 系统用例分析2.5本章小结3......
  • 【Redis】掌握Java中的Redis魔法:Jedis与Spring Data Redis(实战指南)
    文章目录掌握Java中的Redis魔法:Jedis与SpringDataRedis实战文章简介为什么使用Redis为什么选择Jedis和SpringDataRedis一、引言1.1Redis简介1.1.1Redis的特点和优势1.1.2Redis的应用场景1.2Java与Redis的结合1.2.1为什么选择Java1.2.2Java开发中Redis的重要......
  • springboot高校实验室安全管理系统-计算机毕业设计源码73839
    目 录摘要1绪论1.1研究背景1.2 选题意义1.3研究方案1.4论文章节安排2相关技术介绍2.1B/S结构2.2SpringBoot框架2.3Java语言2.4MySQL数据库3系统分析3.1可行性分析3.2 系统功能性分析3.3.非功能性分析3.4 系统用例分析3.5系统......
  • springboot电影院购票管理系统-计算机毕业设计源码71301
    目 录摘要1绪论1.1选题背景与意义1.2开发现状1.3论文结构与章节安排2 电影院购票管理系统系统分析2.1可行性分析2.1.1技术可行性分析2.1.2 经济可行性分析2.1.3操作可行性分析2.2系统功能分析2.2.1功能性分析2.2.2非功能性分析2.3 ......
  • Playwright.Net下载文件 WaitForDownloadAsync
    Playwright.Net下载文件,使用WaitForDownloadAsync Playwright=awaitMicrosoft.Playwright.Playwright.CreateAsync();varuserDataDir=$"{Directory.GetCurrentDirectory()}\\UserData";varcontext=awaitPlaywright.Chromium.LaunchPersist......
  • 计算机毕业设计必看必学! ! 89344 springboot大学生就业管理系统,原创定制程序, java、
    摘 要信息化社会内需要与之针对性的信息获取途径,但是途径的扩展基本上为人们所努力的方向,由于站在的角度存在偏差,人们经常能够获得不同类型信息,这也是技术最为难以攻克的课题。针对大学生就业管理系统等问题,对大学生就业管理系统进行研究分析,然后开发设计出大学生就业管理......
  • SpringBoot3 登录管理实现
    一、背景知识1.认证方案概述有两种常见的认证方案,分别是基于Session的认证和基于Token的认证,下面逐一进行介绍基于Session基于Session的认证流程如下图所示该方案的特点登录用户信息保存在服务端内存中,若访问量增加,单台节点压力会较大随用户规模增大,若后台升级为集......
  • SpringBoot 使用策略+工厂模式的几种实现方式
    SpringBoot使用策略+工厂模式的几种实现方式  1.方式一:  2.方式二:使用Spring依赖注入   用过Spring的肯定也离不开这个注解,通过这个注解可以帮我们自动注入我们想要的Bean。除了这个基本功能之外,@Autowired还有更加强大的功能,还可以注入指定类型的数组,Lis......
  • iPhone官方商店软件下载---免费看各种剧第③弹【iOS版包括iPad】
    ①点击iPhone自带软件AppStore②点击搜索,输入“便利阅读”,点击下载到手机 ③进入软件页面后,我们需要激活页面,点击“feedback” ④在反馈界面输入“真厉害”,点击“提交”⑤等软件闪退后,再点击重新进入,就可出现如下页面 ......