Java前后端文件下载怎么实现?
后端,所有的文件,无论MP3,PDF,Excel,png等都是二进制文件,二进制文件在Java中是字节流对象,而字节流对象可以通过输入输出流读写操作,所以文件直接生成二进制对象,通过输出流写出这个对象即可。
同时在响应设置参数,告诉前端是什么文件,怎么下载,是什么编码才匹配不会乱码。
前端,拿到"响应"解析,根据响应设置,打开一个链接让浏览器执行"下载功能"。
整个流程打个比方:打个比方就是按照响应里面的说明书,把二进制文件(bolo)重新组装。
打个比方:你在网上下单买个大衣柜(发送下载请求),商家打包发货(将文件对象用OutputStream打包发货给你),快递运输(response.getWriter().write响应写出去),拿到货打开快递,阅读说明书(前端读取响应对象配置说明),根据说明书组装成衣柜(根据设置说明,将二进制文件(bolo)解析还原成文件)。这就是文件下载。
开发业务场景:功能--->批量导入用户消息。我们在学校做个图书管理系统,那么现在学校要分配账号给学生,不可能一个个设置,那么让学生自己填学号,姓名,班级等信息,然后取部分字段作为账号,初始设置密码,直接导入系统生成账号密码,后边学生就不用手动注册账号密码,而是由学校分配,学校不可能自己一个个设置。所以这样的业务场景就是用一个excel表格导入系统,系统遍历excel然后转化成对象,插入数据库。
后端代码:(EasyExcel是一个工具,专门处理Excel表格的),依赖如下。
xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>
java
@GetMapping("/downloadTemplate")
public void downloadTemplate(HttpServletResponse response) throws IOException {
// 必须设置跨域放行响应头
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("userImportTemplate.xlsx", StandardCharsets.UTF_8);
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
List<UserImportDto> dataList = new ArrayList<>();
// 示例数据
UserImportDto example1 = new UserImportDto();
example1.setUserName("zhang san");
example1.setRealName("张三");
example1.setRole("学生");
example1.setClassName("计算机科学与技术1班");
dataList.add(example1);
UserImportDto example2 = new UserImportDto();
example2.setUserName("lisi");
example2.setRealName("李四");
example2.setRole("教师");
example2.setClassName("软件工程系");
dataList.add(example2);
EasyExcel.write(response.getOutputStream(), UserImportDto.class)
.sheet("用户导入模板")
.doWrite(dataList);
}
浏览器默认暴露的响应头(白名单)
浏览器跨域请求时,默认只暴露这 6 个响应头:
txt
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
其他的都不给前端拿!
Content-Disposition不在白名单里 ❌- 浏览器拿到了,但不让你前端读取
- 所以
response.headers['content-disposition']是undefined,其他我们设置的,不在白名单的浏览器就无法按我们设置的解析,就拿不到对应的数据,比如文件名称拿不到。类似于,没有完整说明书,组装出来的衣柜不完整。
用大白话给你解释:
1. response.setContentType()
java
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
翻译:告诉浏览器 "我发给你的是一个 Excel 文件"
| 参数 | 含义 |
|---|---|
application/ |
这是一个"应用程序文件",不是网页(text/html) |
vnd.openxmlformats-officedocument.spreadsheetml.sheet |
这是 .xlsx 格式的 Excel 文件 |
常见对照:response.setContentType可以设置的参数对照
java
// Excel 2007+ (.xlsx)
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
// Excel 2003 (.xls)
"application/vnd.ms-excel"
// PDF
"application/pdf"
// 普通文本
"text/plain"
// Word (.docx)
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
// 图片
"image/jpeg"
"image/png"
// ZIP 压缩包
"application/zip"
浏览器收到后会怎么做?
- 看到
application/pdf→ 知道是 PDF → 要么下载,要么在浏览器打开 - 看到
text/html→ 知道是网页 → 直接渲染
2. response.setCharacterEncoding()
java
response.setCharacterEncoding("utf-8");
翻译:用 UTF-8 编码来传数据
什么意思?
中文不乱码 → 依赖这个
| 编码 | 效果 |
|---|---|
UTF-8 |
支持中文,全球通用 ✅ |
GBK |
支持中文,但国外软件可能乱码 |
ISO-8859-1 |
不支持中文,全是乱码 ❌ |
3. response.setHeader("Content-Disposition", ...)
java
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
翻译:告诉浏览器 "这个文件要下载,文件名是 xxx"
| 部分 | 含义 |
|---|---|
attachment |
要下载,不要在浏览器打开 |
filename=xxx.xlsx |
保存时的默认文件名 |
对比:
java
// 下载(弹窗保存)
"attachment; filename=文件.xlsx"
// 浏览器:弹出下载框,默认文件名是"文件.xlsx"
// 直接打开(浏览器内预览)
"inline; filename=文件.pdf"
// 浏览器:直接在浏览器打开(PDF 会预览)
完整流程大白话
java
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
// ① "喂浏览器,我发的是 Excel 文件"
response.setCharacterEncoding("utf-8");
// ② "我用 UTF-8 编码,别乱码"
response.setHeader("Content-Disposition", "attachment; filename=用户导入模板.xlsx");
// ③ "你要下载保存,名字叫'用户导入模板.xlsx'"
response.getOutputStream().write(excelData);
// ④ "给你文件内容(二进制数据)"
浏览器收到后:
- 看到 Content-Type → 知道是 Excel
- 看到 Content-Disposition → 知道要下载
- 看到文件名 → 用这个名字保存
- 收到数据 → 生成文件
前端收到的是什么?
前端用 responseType: 'blob' 接收:
javascript
const blob = response.data
// blob 就是文件的二进制数据(0101010101...)
// 里面包含了 Excel 的所有内容
const url = URL.createObjectURL(blob)
// 把二进制数据变成一个浏览器能访问的临时地址
a.href = url
a.download = filename
// 告诉浏览器:下载这个地址的内容,文件名是 xxx
a.click()
// 触发下载
刚刚后端响应设置的了Disposition大写 ,前端设置小写影响吗?const contentDisposition = response.headers?.'content-disposition'
不影响! HTTP 响应头是大小写不敏感的。
前端代码:
javascript
export const downloadBatchTemplate = (url, params = {}, method = 'get') => {
// 这个request是请求对象,请求后会回调响应,她两是一对的,所以.then(reponse)其中的response就是返回的响应对象--->打包的快递
return request({
url,
method,
params,
responseType: 'blob'
}).then((response) => { // 这里接收到的是完整的 response 对象
const blob = response.data // 从 response 中取出 blob 文件
console.log('所有响应头:', response.headers)
const link = document.createElement('a') // 创建a标签
const blobUrl = URL.createObjectURL(blob) // 创建打开的浏览器路径
link.href = blobUrl
// 从 response.headers 中获取文件名
const contentDisposition = response.headers?.['content-disposition']
let filename = 'importBatchTemplate.xlsx' // 默认文件名,如果读取响应头失败拿不到文件名给个默认的
if (contentDisposition) {
const match = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)
if (match && match[1]) {
filename = decodeURIComponent(match[1].replace(/['"]/g, '')) // 去掉引号,解码 URL 编码(防止中文乱码)
}
}
link.download = filename // 设置下载文件名
document.body.appendChild(link) // 兼容 Firefox
link.click() // 模拟人为点击链接
document.body.removeChild(link) // 清理 DOM
URL.revokeObjectURL(blobUrl) // 释放内存,防止内存泄漏
})
}
总结
| 代码 | 英文 | 中文意思 |
|---|---|---|
setContentType |
Content Type | 告诉浏览器"这是啥类型文件" |
setCharacterEncoding |
Character Encoding | 用啥编码,防止乱码 |
setHeader("Content-Disposition") |
Content Disposition | 告诉浏览器"下载还是预览" |
简单说就是:告诉浏览器这是什么文件,叫什么名字,要下载还是打开。
当然你可以弄一个通用的下载方法然后导出去,调用的时候只需要传入请求方法和地址即可。