文件下载源代码

import io.swagger.annotations.Api;
import org.slf4j.Logger;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
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.net.URLEncoder;

import static java.lang.invoke.MethodHandles.lookup;
import static org.slf4j.LoggerFactory.getLogger;

@Api(tags = "下载文件")
@RestController
@RequestMapping("/download")
public class DownloadController
{
private static final Logger LOG = getLogger(lookup().lookupClass());

@GetMapping("/download_excel_template")
public void downloadFile(HttpServletResponse response) throws IOException {
InputStream inputStream = null;
OutputStream out = null;
try {
Resource resource = new ClassPathResource("file/七星区人事信息模板.xlsx");
File file = resource.getFile();
String filename = resource.getFilename();
inputStream = new FileInputStream(file);
//强制下载不打开
response.setContentType("application/octet-stream");
out = response.getOutputStream();
//使用URLEncoder来防止文件名乱码或者读取错误
response.setHeader("Content-Disposition", "attachment; filename="+ URLEncoder.encode(filename, "UTF-8"));
//当不写上一行代码时, 浏览器读到有后缀的文件时会尝试打开;即使下载也会命名为file。
//加上设置大小下载下来的.xlsx文件打开时才不会报“Excel 已完成文件级验证和修复。此工作簿的某些部分可能已被修复或丢弃”
response.addHeader("Content-Length", String.valueOf(file.length()));
int b = 0;
byte[] buffer = new byte[1000000];
while (b != -1) {
b = inputStream.read(buffer);
if(b!=-1) out.write(buffer, 0, b);
}
LOG.info("下载模板Excel成功");
// return new Response(200, "下载成功");
} catch (IOException e) {
e.printStackTrace();
// return new Response("下载失败, 请重新下载");
} finally {
inputStream.close();
out.close();
out.flush();
}
}
}

如果下载方法有返回值的话, 会报错

rg.springframework.http.converter.HttpMessageNotWritableException: No converter for [class java.lang.Boolean] with preset Content-Type 'application/octet-stream;charset=UTF-8'

java.lang.IllegalStateException: Cannot call sendError() after the response has been committed

解决方法是去掉返回值, 或者return null;

文件下载总结

在文件下载功能中,一般都会借助于这两个 header 来达到效果,那么两个 header 的具体作用是什么呢?

  • Content-Type:告诉浏览器当前的响应体是个什么类型的数据。当其为 application/octet-stream 的时候,就说明 body 里面是一堆不知道是啥的二进制数据。
  • Content-Disposition:用于向浏览器提供一些关于如何处理响应内容的额外的信息,同时也可以附带一些其它数据,比如在保存响应体到本地的时候应该使用什么样的文件名。

细想一下, Content-Type 好像对于文件下载没什么作用?事实上的确如此。可是再想一下,如果浏览器不理会 Content-Disposition ,不下载文件怎么办?如果此时提供了 Content-Type ,至少浏览器还有机会根据具体的 Content-Type 对响应体进行处理。

可是为什么浏览器会不理会 Content-Disposition 呢?因为这个 Content-Disposition 头部并不是 HTTP 标准中的内容,只是被浏览器广泛实现的一个 header 而已。

话题转一转, Content-Disposition 的语法见此处,其中相对重要的点此处罗列一下:

  • 常用的 disponsition-type 有 inlineattachment
    • inline:建议浏览器使用默认的行为处理响应体。
    • attachment:建议浏览器将响应体保存到本地,而不是正常处理响应体。
  • Content-Disposition 中可以传入 filename 参数,有两种形式:
    • filename=yourfilename.suffix:直接指明文件名和后缀。
    • filename*=utf-8’’yourfilename.suffix:指定了文件名编码。其中,编码后面那对单引号中还可以填入内容,此处不赘述,可参考规范
    • 有些浏览器不认识 filename*=utf-8''yourfilename.suffix (估计因为这东西比较复杂),所以最好带上 filename=yourfilename.suffix

使用PostMan测试文件下载接口

配不配置Header皆可