SpringMVC 下载文本实现边转码边下载

边转码边下载

下载通常都会遇到这种情况,代码里通常保存为 utf-8 格式,但是用户想要 gbk 格式,实现思路如下:

方案一: PrintWriter + CharacterEncoding

SpringMVC 框架的接口中 Response 有 ServletOutputStreamPrintWriter 两种输出,还有一个 setCharacterEncoding 方法。

OutPutStream 视为写入二进制数据,容器不对二进制数据进行编码,即 setCharacterEncoding 无效。所以先采用 PrintWriter 搭配 setCharacterEncoding 方法。

@GetMapping("/downloadFile1")
public void downloadFile1(HttpServletResponse response) throws IOException{

    //读取本地文件
    File file = new File(LOCAL_FILE_PATH);
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));

    //设置下载
    response.setHeader("Content-Disposition", "attachment;filename=file.csv");
    response.setContentType("application/octet-stream");
    response.setCharacterEncoding("GB2312");

    //传输
    BufferedWriter bufferedWriter = new BufferedWriter(response.getWriter());
    String line;
    while ((line = bufferedReader.readLine()) != null) {
        bufferedWriter.write(line + "\r\n");
        bufferedWriter.flush();
    }
    bufferedReader.close();
    bufferedWriter.close();

}

无用,没有仔细研究,应该 setCharacterEncoding 只是告诉浏览器是什么编码,没有解码功能。

方案二:ServletOutputStream + OutputStreamWriter

response.getOutputStream() 获取到的流用 OutputStreamWriter 包装。结果可行。

@GetMapping("/downloadFile2")
public void downloadFile2(HttpServletResponse response) throws IOException{

    //读取本地文件
    File file = new File(LOCAL_FILE_PATH);
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));

    //设置下载
    response.setHeader("Content-Disposition", "attachment;filename=file.csv");
    response.setContentType("application/octet-stream");

    //传输
    OutputStreamWriter writerStream = new OutputStreamWriter(response.getOutputStream(),"GBK");
    String line;
    while ((line = bufferedReader.readLine()) != null) {
        writerStream.write(line + "\r\n");
        writerStream.flush();
    }
    bufferedReader.close();
    writerStream.close();

}

方案三:ServletOutputStream + getBytes

在按行写入字符串时,使用 getBytes 方法将字符串转换为想要输出的编码格式。结果可行。

@GetMapping("/downloadFile3")
public void downloadFile3(HttpServletResponse response) throws IOException{

    //读取本地文件
    File file = new File(LOCAL_FILE_PATH);
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));

    //设置下载
    response.setHeader("Content-Disposition", "attachment;filename=file.csv");
    response.setContentType("application/octet-stream");

    //传输
    OutputStream outputStream = response.getOutputStream();
    String line;
    while ((line = bufferedReader.readLine()) != null) {
        outputStream.write((line + "\r\n").getBytes("GB2312"));
        outputStream.flush();
    }
    bufferedReader.close();
    outputStream.close();
}

无需转换的写法

若不需要转码的话,就很简短了,用字节流就行了。

@GetMapping("/downloadFile4")
public void downloadFile4(HttpServletResponse response) throws IOException{

    //读取本地文件
    File file = new File(LOCAL_FILE_PATH);
    FileInputStream fis = new FileInputStream(file);

    //设置下载
    response.setHeader("Content-Disposition", "attachment;filename=file.csv");
    response.setContentType("application/octet-stream");

    //传输
    OutputStream os = response.getOutputStream();
    byte[] buffer = new byte[4096];
    int b = -1;
    while ((b = fis.read(buffer)) != -1) {
        os.write(buffer, 0, b);
    }
    fis.close();
    os.close();
}

简写的话可以用第三方包。

import org.springframework.util.FileCopyUtils;
@GetMapping("/downloadFile5")
public void downloadFile5(HttpServletResponse response) throws IOException{

    //读取本地文件
    File file = new File(LOCAL_FILE_PATH);
    InputStream inputStream= new BufferedInputStream(new FileInputStream(file));

    //设置下载
    response.setHeader("Content-Disposition", "attachment;filename=file.csv");
    response.setContentType("application/octet-stream");

    //传输
    FileCopyUtils.copy(inputStream, response.getOutputStream());


}

多文件打包下载

当需要下载多个文件时,可以将其打包成一个文件,然后下载。Java 自带的 ZipOutputStream 可以实现。

import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Override
public void download(OutputStream outputStream) throws IOException {

    // Create the ZIP file
    ZipOutputStream zipOut = new ZipOutputStream(outputStream);

    // image demo
    Map<String, String> imageMap = new HashMap<>;

    //add file to zip
    ZipEntry zipEntry;
    for (String imageName : imageMap.keySet()) {
        // http://192.168.0.102:13080/pcg/files/image/64280347-afa8-4057-8941-ccbaeebf3c4b
        File fileToZip = new File(StaticProperty.IMAGE_PATH, imageMap.get(imageName));

        try (FileInputStream fis = new FileInputStream(fileToZip)) {
            zipEntry = new ZipEntry(imageName);

            zipOut.putNextEntry(zipEntry);

            byte[] bytes = new byte[1024];
            int length;
            while((length = fis.read(bytes)) >= 0) {
                zipOut.write(bytes, 0, length);
            }

        }catch (FileNotFoundException fnfe){
            LOG.warn("file {} not found", imageMap.get(imageName));
        }

        zipOut.closeEntry();
    }

    zipOut.flush();
    zipOut.close();

补充:

  • 使用 Nginx 可以不通过接口前端直接下载,后续可以尝试采用此方法。

Last modified on 2020-03-04