原理讲解

在Spring Boot应用程序中,如果您尝试将应用程序打包成一个 JAR 并运行,那么您不能使用FileFileInputStream来直接读取 JAR 内部的文件,因为这些文件不是以传统文件系统的形式存在的。相反,它们被嵌入到了 JAR 文件中,必须通过类加载器来访问。那么您应该始终使用类路径访问方式(ClassLoader.getResourceAsStream或Spring的ResourceLoader),而不是尝试直接访问文件系统路径。

浏览器加载文件是依据响应头信息:

后端设置响应头:在后端代码中,需要设置正确的响应头信息,包括 Content-Type 和 Content-Disposition。这些头部信息告诉浏览器如何处理接收到的数据。只要你设置了,浏览器得到响应后下载后,就有对应设置好的文件名!

响应头设置

Content-Type

  1. application/octet-stream:通用的二进制流文件类型,适用于未知文件类型的下载。
  2. application/pdf:PDF 文件。
  3. image/jpeg:JPEG 图像文件。
  4. image/png:PNG 图像文件。
  5. text/plain:纯文本文件。
  6. application/zip:ZIP 文件,用于压缩文件的下载。
  7. application/json:JSON 格式文件。

Content-Disposition

  1. inline:浏览器会尝试直接打开文件,例如在浏览器中显示 PDF 文件。
  2. attachment:提示浏览器下载文件,而不是直接打开。可以指定文件名。
  3. filename="你的名称.文件类型":比如指定下载文件的文件名为 "example.txt"。注意名称不允许用中文,如果直接使用中文,会出现整个响应头的内容被吞!一般采用URLEncoder.encode(resource.getFilename(), "UTF-8")处理一下为URL编码,这样就可以实现包含中文名的下载了

示例一:读取文件内容 + 下载文件

这个文件放在src/main/resource文件夹下放了一个dapdownload文件夹,放置一个文件叫:mock.txt,这里是读取该文本并返回。

注意打包后,需要确定相关资源在jar包内部!


    @GetMapping("/getXXProjectLists")
    @Operation(summary = "将文件内容当做响应内容返回")
    private String getXXProjectLists() {
        /*
        下面2个方式在打成jar包后,是无法找到文件的!所以仅供本地idea调试的使用使用。
        return FileUtil.readUtf8String("dapdownload/mock.txt");
        return FileUtil.readUtf8String(new ClassPathResource("dapdownload/mock.txt").getPath());
        */
        try {
            ClassPathResource classPathResource = new ClassPathResource("dapdownload/mock.txt");
            // 使用StreamUtils来从InputStream中读取字符串
            String content = StreamUtils.copyToString(classPathResource.getInputStream(), StandardCharsets.UTF_8);
            return content;
        } catch (IOException e) {
            e.printStackTrace();
            return "Error reading file";
        }
    }

    @GetMapping("/downloadFile")
    @Operation(summary = "下载XX文件")
    public void downloadFile(HttpServletResponse response, String fileName) {
        try {
            Resource resource = new ClassPathResource("dapdownload/" + fileName); // 文件名可以根据实际情况更改
            // 设置响应内容类型
            String contentType = "application/octet-stream";
            if (resource.exists()) {
                contentType = determineContentType(resource);
            }
            response.setContentType(contentType);

            // 设置响应头,指定文件名
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(resource.getFilename(), "UTF-8")); // 不能有后缀

            // 读取文件并写入响应输出流
            InputStream inputStream = resource.getInputStream();
            OutputStream outputStream = response.getOutputStream();
            byte[] buffer = new byte[4096];
            int len;
            while ((len = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }
            outputStream.flush();
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
            // 可以添加适当的异常处理
        }
    }

    private String determineContentType(Resource resource) throws IOException {
        return MediaType.APPLICATION_OCTET_STREAM_VALUE;
        // 在实际情况下,您可能需要使用更复杂的逻辑来确定文件的MIME类型
    }

给一个GPT处理过的代码!

为了确保下载文件名中的特殊字符(如空格)被正确处理,您应该将文件名放在双引号中,并且考虑对文件名进行URL编码,以防止HTTP头注入攻击或其他问题:这里使用了 URLEncoder.encode 对文件名进行URL编码,并替换了编码过程中产生的加号(+),因为加号在URL编码中表示空格。filename* 语法允许指定字符编码和语言,确保非ASCII字符的文件名可以被正确处理。

        String fileName;
        // 获取当前的年月日时分秒
        LocalDateTime now = LocalDateTime.now();
        // 定义日期时间格式
//        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); // 真奇怪 还喜欢这个格式
        // 格式化为字符串
        fileName = archDefine.getArchName()+"模版"+now.format(formatter);

        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName + ".xlsx");

更简便的代码实现

fileName = 导入模版.xlsx

文件放在 resources/static/fileTemplate/导入模版.xlsx

    @Override
    public void downloadTemplate(String fileName) {
        if (StringUtils.isBlank(fileName)) {
            throw new RuntimeException("请输入文件名");
        }
        // https://www.zanglikun.com/19445.html 为什么resource无法下载
        ClassPathResource classPathResource = new ClassPathResource("static/fileTemplate/" + fileName);
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName.split(",")[0], "UTF-8")); // 设置响应头,告诉浏览器将响应内容作为附件下载,并指定下载文件的名称
            response.setContentType("application/octet-stream"); // 设置响应的内容类型为 application/octet-stream,表示响应内容是一个二进制流
            inputStream = classPathResource.getInputStream();
            outputStream = response.getOutputStream();
            IoUtil.copy(classPathResource.getInputStream(), response.getOutputStream()); // Hutool方法说 使用默认的缓冲区大小,完成后不会关流
        } catch (Exception e) {
            log.error("下载模版出现异常,错误信息如下{}", ExceptionUtils.getMessage(e));
            throw new RuntimeException("下载模版出现异常");
        } finally {
            closeStream(inputStream, outputStream);
        }
    }
特殊说明:
上述文章均是作者实际操作后产出。烦请各位,请勿直接盗用!转载记得标注原文链接:www.zanglikun.com
第三方平台不会及时更新本文最新内容。如果发现本文资料不全,可访问本人的Java博客搜索:标题关键字。以获取最新全部资料 ❤