Press "Enter" to skip to content

java 以流的方式提供文件下载

背景

有个项目需要导出数据库数据为Excel,实践过程中发现,先要从数据库取数据,再拼装成Excel文件,再发送给用户,整个过程非常长,用户需要在网页卡很久才会出现下载框,体验很差。

希望做到数据分页的从数据库查出来,直接发给用户,查多少发多少,提升用户体验。

方案

我们拿到HttpServletResponse的OutputStream,采用流的方式向OutputStream里面直接写入数据实现想要的功能。

package com.example.demo;

import lombok.extern.slf4j.Slf4j;

import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@RestController
@RequestMapping("/")
@Slf4j
public class Controller {

    @GetMapping("/")
    public void doGet(HttpServletResponse resp)
            throws IOException {

        resp.setContentType("application/octet-stream");
        resp.setHeader("Content-disposition", "attachment; filename=sample.txt");

        try(InputStream in = new FileInputStream(ResourceUtils.getFile("classpath:sample.txt"));
            OutputStream out = resp.getOutputStream()) {

            byte[] buffer = new byte[10];

            int numBytesRead;
            while ((numBytesRead = in.read(buffer)) > 0) {
                try {
                    Thread.sleep(100);
                    log.info("模拟从数据库取数据...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                out.write(buffer, 0, numBytesRead);
                out.flush();
            }
        }
    }

    @GetMapping("/csvFile")
    public void getCSVFile(HttpServletResponse resp)  throws IOException {

        List<String[]> dataLines = new ArrayList<>();
        dataLines.add(new String[]
                { "John", "Doe", "38", "Comment Data\nAnother line of comment data" });
        dataLines.add(new String[]
                { "Jane", "Doe, Jr.", "19", "She said \"I'm being quoted\"" });
        dataLines.add(new String[]
                { "Jane", "Doe, Jr.", "19", "She said \"I'm being quoted\"" });
        dataLines.add(new String[]
                { "Jane", "Doe, Jr.", "19", "She said \"I'm being quoted\"" });
        dataLines.add(new String[]
                { "Jane", "Doe, Jr.", "19", "She said \"I'm being quoted\"" });
        dataLines.add(new String[]
                { "Jane", "Doe, Jr.", "19", "She said \"I'm being quoted\"" });

        resp.setContentType("application/octet-stream");
        resp.setHeader("Content-disposition", "attachment; filename=sample.csv");

        try (OutputStream out = resp.getOutputStream()) {

            for(String[] data : dataLines) {
                final String s = this.convertToCSV(data) + System.lineSeparator();
                out.write(s.getBytes(Charset.forName("UTF-8")));
                out.flush();
                try {
                    // 模拟数据库延迟
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    public String convertToCSV(String[] data) {
        return Stream.of(data)
                .map(this::escapeSpecialCharacters)
                .collect(Collectors.joining(","));
    }

    public String escapeSpecialCharacters(String data) {
        String escapedData = data.replaceAll("\\R", " ");
        if (data.contains(",") || data.contains("\"") || data.contains("'")) {
            data = data.replace("\"", "\"\"");
            escapedData = "\"" + data + "\"";
        }
        return escapedData;
    }
}

参考资料:

https://www.baeldung.com/java-csv

https://stackoverflow.com/questions/4069028/write-string-to-output-stream

https://www.baeldung.com/servlet-download-file

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注