从 Spring Boot Rest 服务下载文件

Posted

技术标签:

【中文标题】从 Spring Boot Rest 服务下载文件【英文标题】:download a file from Spring boot rest service 【发布时间】:2016-06-11 09:26:01 【问题描述】:

我正在尝试。

@RequestMapping(path="/downloadFile",method=RequestMethod.GET)
    @Consumes(MediaType.APPLICATION_JSON_VALUE)
    public  ResponseEntity<InputStreamReader> downloadDocument(
                String acquistionId,
                String fileType,
                Integer expressVfId) throws IOException 
        File file2Upload = new File("C:\\Users\\admin\\Desktop\\bkp\\1.rtf");
        HttpHeaders headers = new HttpHeaders();
        headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
        headers.add("Pragma", "no-cache");
        headers.add("Expires", "0");
        InputStreamReader i = new InputStreamReader(new FileInputStream(file2Upload));
        System.out.println("The length of the file is : "+file2Upload.length());

        return ResponseEntity.ok().headers(headers).contentLength(file2Upload.length())
                .contentType(MediaType.parseMediaType("application/octet-stream"))
                .body(i);
        

当我尝试从浏览器下载文件时,它会开始下载,但总是失败。导致下载失败的服务有什么问题吗?

【问题讨论】:

【参考方案1】:

选项 1 使用 InputStreamResource

Resource 实现给定的InputStream。

仅应在没有其他特定资源实现适用的情况下使用。特别是,尽可能首选 ByteArrayResource 或任何基于文件的 Resource 实现。

@RequestMapping(path = "/download", method = RequestMethod.GET)
public ResponseEntity<Resource> download(String param) throws IOException 

    // ...

    InputStreamResource resource = new InputStreamResource(new FileInputStream(file));

    return ResponseEntity.ok()
            .headers(headers)
            .contentLength(file.length())
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .body(resource);


Option2 正如 InputStreamResource 的文档所建议的那样 - 使用 ByteArrayResource:

@RequestMapping(path = "/download", method = RequestMethod.GET)
public ResponseEntity<Resource> download(String param) throws IOException 

    // ...

    Path path = Paths.get(file.getAbsolutePath());
    ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path));

    return ResponseEntity.ok()
            .headers(headers)
            .contentLength(file.length())
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .body(resource);

【讨论】:

我正在尝试为 word 文档 .doc 格式执行此操作,但是在下载格式时已消失,并且下载的文件没有文件扩展名,并且下载时文件名是响应。有什么建议吗? @TulsiJain 添加 Content-Disposition HttpHeader:HttpHeaders headers = new HttpHeaders(); headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=myDoc.docx"); 以防万一您不幸使用普通 Spring 而不是 Spring Boot,您需要确保将 ResourceHttpMessageConverter 的实例添加到您的 HttpMessageConverters 列表中。创建一个扩展WebMvcConfigurerAdapter@Configuration 类,实现configureMessageConverters() 方法并添加converters.add(new ResourceHttpMessageConverter()); 问题:选项 1 似乎没有关闭流。魔法在哪里?选项 2 似乎在发送之前将完整的文件加载到内存中。正确的?备择方案?谢了! @eventhorizo​​n 在选项 1 中,Spring Boot 将关闭流。看到这个:***.com/a/48660203/7363182【参考方案2】:

下面的示例代码对我有用,可能会对某人有所帮助。

import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@RestController
@RequestMapping("/app")
public class ImageResource 

    private static final String EXTENSION = ".jpg";
    private static final String SERVER_LOCATION = "/server/images";

    @RequestMapping(path = "/download", method = RequestMethod.GET)
    public ResponseEntity<Resource> download(@RequestParam("image") String image) throws IOException 
        File file = new File(SERVER_LOCATION + File.separator + image + EXTENSION);

        HttpHeaders header = new HttpHeaders();
        header.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=img.jpg");
        header.add("Cache-Control", "no-cache, no-store, must-revalidate");
        header.add("Pragma", "no-cache");
        header.add("Expires", "0");

        Path path = Paths.get(file.getAbsolutePath());
        ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path));

        return ResponseEntity.ok()
                .headers(header)
                .contentLength(file.length())
                .contentType(MediaType.parseMediaType("application/octet-stream"))
                .body(resource);
    


【讨论】:

非常适合我。【参考方案3】:

我想分享一个使用 javascript (ES6)、ReactSpring Boot 后端下载文件的简单方法:

    Spring boot 休息控制器

来自org.springframework.core.io.Resource的资源

    @SneakyThrows
    @GetMapping("/files/filename:.+/extraVariable")
    @ResponseBody
    public ResponseEntity<Resource> serveFile(@PathVariable String filename, @PathVariable String extraVariable) 

        Resource file = storageService.loadAsResource(filename, extraVariable);
        return ResponseEntity.ok()
               .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"")
               .body(file);
    
    React,API 调用使用AXIOS

将 responseType 设置为 arraybuffer 以指定响应中包含的数据类型。

export const DownloadFile = (filename, extraVariable) => 
let url = 'http://localhost:8080/files/' + filename + '/' + extraVariable;
return axios.get(url,  responseType: 'arraybuffer' ).then((response) => 
    return response;
);

最后一步 > 下载 在js-file-download 的帮助下,您可以触发浏览器将数据保存到文件中,就像下载数据一样。

DownloadFile('filename.extension', 'extraVariable').then(
(response) => 
    fileDownload(response.data, filename);

, (error) => 
    // ERROR 
);

【讨论】:

我遇到了这个问题,并对 CONTENT_DISPOSITION 标头的文件名用双引号括起来感到好奇。事实证明,如果您有一个名称中包含空格的文件,那么如果没有双引号,您将无法在响应中获得整个文件名。好电话,@fetahokey【参考方案4】:
    @GetMapping("/downloadfile/productId/fileName")
public ResponseEntity<Resource> downloadFile(@PathVariable(value = "productId") String productId,
        @PathVariable String fileName, HttpServletRequest request) 
    // Load file as Resource
    Resource resource;

    String fileBasePath = "C:\\Users\\v_fzhang\\mobileid\\src\\main\\resources\\data\\Filesdown\\" + productId
            + "\\";
    Path path = Paths.get(fileBasePath + fileName);
    try 
        resource = new UrlResource(path.toUri());
     catch (MalformedURLException e) 
        e.printStackTrace();
        return null;
    

    // Try to determine file's content type
    String contentType = null;
    try 
        contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
     catch (IOException ex) 
        System.out.println("Could not determine file type.");
    

    // Fallback to the default content type if type could not be determined
    if (contentType == null) 
        contentType = "application/octet-stream";
    

    return ResponseEntity.ok().contentType(MediaType.parseMediaType(contentType))
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
            .body(resource);

要测试它,请使用邮递员

http://localhost:8080/api/downloadfile/GDD/1.zip

【讨论】:

【参考方案5】:

我建议使用 StreamingResponseBody,因为有了它,应用程序可以直接写入响应 (OutputStream),而不会阻塞 Servlet 容器线程。如果您要下载非常大的文件,这是一个很好的方法。

@GetMapping("download")
public StreamingResponseBody downloadFile(HttpServletResponse response, @PathVariable Long fileId) 

    FileInfo fileInfo = fileService.findFileInfo(fileId);
    response.setContentType(fileInfo.getContentType());
    response.setHeader(
        HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=\"" + fileInfo.getFilename() + "\"");

    return outputStream -> 
        int bytesRead;
        byte[] buffer = new byte[BUFFER_SIZE];
        InputStream inputStream = fileInfo.getInputStream();
        while ((bytesRead = inputStream.read(buffer)) != -1) 
            outputStream.write(buffer, 0, bytesRead);
        
    ;

Ps.:在使用StreamingResponseBody时,强烈建议配置Spring MVC中使用的TaskExecutor来执行异步请求。 TaskExecutor 是一个抽象 Runnable 执行的接口。

更多信息:https://medium.com/swlh/streaming-data-with-spring-boot-restful-web-service-87522511c071

【讨论】:

【参考方案6】:

如果您需要从服务器的文件系统下载一个巨大的文件,那么 ByteArrayResource 可以占用所有 Java 堆空间。在这种情况下,您可以使用FileSystemResource

【讨论】:

【参考方案7】:

使用 Apache IO 可能是复制 Stream 的另一种选择

@RequestMapping(path = "/file/fileId", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> downloadFile(@PathVariable(value="fileId") String fileId,HttpServletResponse response) throws Exception 

    InputStream yourInputStream = ...
    IOUtils.copy(yourInputStream, response.getOutputStream());
    response.flushBuffer();
    return ResponseEntity.ok().build();

maven 依赖

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-io</artifactId>
        <version>1.3.2</version>
    </dependency>

【讨论】:

直接用inputStream发回InputStreamResource。你不需要复制流。

以上是关于从 Spring Boot Rest 服务下载文件的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 spring rest 服务/spring boot 下载 Excel

Spring Boot / REST - 示例代码在启动后终止

Spring boot:有时没有从基于 Rest Api 的服务中得到任何响应

无法从 Spring Boot REST 中的 Hibernate POJO 返回 JSON

无法从 JUnit 中的 Spring Boot 读取应用程序属性?

从 Ajax 调用 Spring Boot Rest API 时出现 CORS 错误