文章目录
- pdf文件下载
-
- 后端代码示例
- 前端处理方案
-
- 方案1:使用Axios处理
- [方案2:使用Fetch API](#方案2:使用Fetch API)
- 方案3:更简洁的Base64处理方式
- 后端优化建议
- 关键点总结
- Excel文件下载
pdf文件下载
在Spring后端使用Map
封装了ResponseEntity<byte[]>
的情况下,前端需要先解析这个Map结构,然后提取出文件数据进行下载。以下是完整的解决方案:
后端代码示例
java
@PostMapping("/download")
public Map<String, Object> downloadFile() {
try {
// 读取文件内容到byte数组
byte[] fileContent = Files.readAllBytes(Paths.get("path/to/your/file.pdf"));
// 设置响应头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
headers.setContentDispositionFormData("attachment", "filename.pdf");
headers.setContentLength(fileContent.length);
// 创建ResponseEntity
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(fileContent, headers, HttpStatus.OK);
// 封装到Map中
Map<String, Object> result = new HashMap<>();
result.put("status", responseEntity.getStatusCodeValue());
result.put("headers", new HashMap<>());
// 转换headers
responseEntity.getHeaders().forEach((key, values) -> {
if (values.size() > 0) {
((Map<String, Object>) result.get("headers")).put(key, values.get(0));
}
});
// 对文件内容进行Base64编码(避免JSON传输二进制数据问题)
result.put("body", Base64.getEncoder().encodeToString(responseEntity.getBody()));
return result;
} catch (Exception e) {
throw new RuntimeException("文件下载失败", e);
}
}
前端处理方案
方案1:使用Axios处理
javascript
import axios from 'axios';
const downloadFile = async () => {
try {
const response = await axios.post('/api/download', {}, {
responseType: 'json' // 重要:先以JSON格式接收
});
const { status, headers, body } = response.data;
if (status === 200) {
// 将Base64字符串转换为Blob
const binaryString = atob(body);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
const blob = new Blob([bytes], {
type: headers['content-type'] || 'application/octet-stream'
});
// 创建下载链接
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
// 从Content-Disposition头中获取文件名
const contentDisposition = headers['content-disposition'];
let filename = 'download.pdf';
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename="?(.+)"?/);
if (filenameMatch) {
filename = filenameMatch[1];
}
}
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}
} catch (error) {
console.error('下载失败:', error);
}
};
方案2:使用Fetch API
javascript
const downloadFileWithFetch = async () => {
try {
const response = await fetch('/api/download', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (data.status === 200) {
// 处理Base64数据
const byteCharacters = atob(data.body);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], {
type: data.headers['content-type']
});
// 下载文件
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
// 获取文件名
const disposition = data.headers['content-disposition'];
const filename = disposition ?
disposition.split('filename=')[1].replace(/"/g, '') :
'file.pdf';
link.download = filename;
link.click();
URL.revokeObjectURL(url);
}
} catch (error) {
console.error('下载失败:', error);
}
};
方案3:更简洁的Base64处理方式
javascript
const downloadFileSimplified = async () => {
try {
const response = await axios.post('/api/download');
const { status, headers, body } = response.data;
if (status === 200) {
// 直接使用fetch下载Base64数据
const blob = await fetch(`data:${headers['content-type']};base64,${body}`)
.then(res => res.blob());
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = getFilenameFromHeaders(headers) || 'file';
a.click();
URL.revokeObjectURL(url);
}
} catch (error) {
console.error('下载失败:', error);
}
};
// 从headers中提取文件名的辅助函数
const getFilenameFromHeaders = (headers) => {
const disposition = headers['content-disposition'];
if (!disposition) return null;
const utf8Filename = disposition.match(/filename\*=UTF-8''(.+)/);
if (utf8Filename) {
return decodeURIComponent(utf8Filename[1]);
}
const filename = disposition.match(/filename="?(.+)"?/);
return filename ? filename[1].replace(/"/g, '') : null;
};
后端优化建议
如果可能,建议后端直接返回ResponseEntity<byte[]>
而不是封装在Map中:
java
@PostMapping("/download-direct")
public ResponseEntity<byte[]> downloadFileDirect() {
byte[] fileContent = // ... 读取文件
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
headers.setContentDispositionFormData("attachment", "filename.pdf");
return new ResponseEntity<>(fileContent, headers, HttpStatus.OK);
}
这样前端处理会更简单:
javascript
const downloadDirect = async () => {
const response = await axios.post('/api/download-direct', {}, {
responseType: 'blob' // 直接接收Blob数据
});
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'filename.pdf');
document.body.appendChild(link);
link.click();
link.remove();
};
关键点总结
- Base64编码:后端需要对二进制数据进行Base64编码,避免JSON传输问题
- 头信息处理:需要正确解析Content-Type和Content-Disposition头
- Blob转换:前端需要将Base64字符串转换回Blob对象
- 文件名提取:从Content-Disposition头中正确提取文件名
选择哪种方案取决于你的具体需求和前后端约束条件。
Excel文件下载
当ResponseEntity里面放的是Excel文件时,处理方式需要特别注意文件类型和编码。以下是完整的解决方案:
后端代码示例
方案1:直接返回Excel流(推荐)
java
@GetMapping("/download-excel")
public ResponseEntity<byte[]> downloadExcel() {
try {
// 创建Excel工作簿
Workbook workbook = new XSSFWorkbook(); // 或 HSSFWorkbook for .xls
Sheet sheet = workbook.createSheet("数据报表");
// 创建表头
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("姓名");
headerRow.createCell(1).setCellValue("年龄");
headerRow.createCell(2).setCellValue("部门");
// 添加数据
Row dataRow = sheet.createRow(1);
dataRow.createCell(0).setCellValue("张三");
dataRow.createCell(1).setCellValue(28);
dataRow.createCell(2).setCellValue("技术部");
// 写入字节数组
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
workbook.write(outputStream);
workbook.close();
byte[] excelBytes = outputStream.toByteArray();
// 设置响应头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDisposition(ContentDisposition.builder("attachment")
.filename("数据报表.xlsx", StandardCharsets.UTF_8)
.build());
headers.setContentLength(excelBytes.length);
return new ResponseEntity<>(excelBytes, headers, HttpStatus.OK);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
方案2:封装在Map中(按你的要求)
java
@PostMapping("/download-excel-map")
public Map<String, Object> downloadExcelInMap() {
try {
// 创建Excel文件
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("数据报表");
// 填充数据...
Row headerRow = sheet.createRow(0);
String[] headers = {"ID", "名称", "数量", "价格"};
for (int i = 0; i < headers.length; i++) {
headerRow.createCell(i).setCellValue(headers[i]);
}
// 示例数据
Object[][] data = {
{1, "商品A", 100, 29.99},
{2, "商品B", 50, 39.99},
{3, "商品C", 75, 19.99}
};
for (int i = 0; i < data.length; i++) {
Row row = sheet.createRow(i + 1);
for (int j = 0; j < data[i].length; j++) {
if (data[i][j] instanceof Number) {
row.createCell(j).setCellValue(((Number) data[i][j]).doubleValue());
} else {
row.createCell(j).setCellValue(data[i][j].toString());
}
}
}
// 转换为字节数组
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
workbook.write(outputStream);
workbook.close();
byte[] excelBytes = outputStream.toByteArray();
// 封装ResponseEntity到Map
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
httpHeaders.setContentDisposition(ContentDisposition.builder("attachment")
.filename("商品数据.xlsx", StandardCharsets.UTF_8)
.build());
Map<String, Object> result = new HashMap<>();
result.put("status", HttpStatus.OK.value());
result.put("headers", new HashMap<>());
// 转换headers
httpHeaders.forEach((key, values) -> {
if (!values.isEmpty()) {
((Map<String, Object>) result.get("headers")).put(key, values.get(0));
}
});
// Excel文件使用Base64编码
result.put("body", Base64.getEncoder().encodeToString(excelBytes));
result.put("filename", "商品数据.xlsx"); // 额外添加文件名字段
return result;
} catch (Exception e) {
Map<String, Object> errorResult = new HashMap<>();
errorResult.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
errorResult.put("error", e.getMessage());
return errorResult;
}
}
前端处理Excel下载
方案1:使用Axios处理Map响应
javascript
import axios from 'axios';
const downloadExcel = async () => {
try {
const response = await axios.post('/api/download-excel-map', {}, {
responseType: 'json'
});
const { status, headers, body, filename } = response.data;
if (status === 200) {
// 处理Base64编码的Excel数据
const byteCharacters = atob(body);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
// 创建下载链接
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
// 获取文件名(优先使用后端返回的filename字段,其次从headers中解析)
let downloadFilename = filename;
if (!downloadFilename && headers['content-disposition']) {
const match = headers['content-disposition'].match(/filename="?(.+)"?/);
downloadFilename = match ? match[1] : 'download.xlsx';
}
link.download = downloadFilename || 'data.xlsx';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
console.log('Excel文件下载完成');
} else {
console.error('下载失败:', response.data.error);
}
} catch (error) {
console.error('请求失败:', error);
}
};
方案2:更简洁的Base64处理方式
javascript
const downloadExcelSimple = async () => {
try {
const response = await axios.post('/api/download-excel-map');
const { status, body, filename } = response.data;
if (status === 200) {
// 直接使用Base64数据创建Blob
const blob = base64ToBlob(body, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename || 'excel_data.xlsx';
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
} catch (error) {
console.error('下载失败:', error);
}
};
// Base64转Blob的辅助函数
const base64ToBlob = (base64, contentType = '', sliceSize = 512) => {
const byteCharacters = atob(base64);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
return new Blob(byteArrays, { type: contentType });
};
方案3:处理不同Excel格式
javascript
const downloadExcelAdvanced = async () => {
try {
const response = await axios.post('/api/download-excel-map');
const { status, headers, body, filename } = response.data;
if (status !== 200) {
throw new Error(`下载失败: ${response.data.error}`);
}
// 根据文件扩展名确定MIME类型
const fileExt = filename ? filename.split('.').pop().toLowerCase() : 'xlsx';
let mimeType;
switch (fileExt) {
case 'xls':
mimeType = 'application/vnd.ms-excel';
break;
case 'xlsx':
default:
mimeType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
}
// 转换并下载
const blob = base64ToBlob(body, mimeType);
downloadBlob(blob, filename || `data.${fileExt}`);
} catch (error) {
console.error('Excel下载错误:', error);
// 可以添加用户通知,如Toast提示
alert('文件下载失败,请重试');
}
};
// 通用的Blob下载函数
const downloadBlob = (blob, filename) => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
// 针对不同浏览器的下载兼容性处理
if (typeof link.download === 'undefined') {
window.open(url);
} else {
link.href = url;
link.download = filename;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// 清理URL对象
setTimeout(() => URL.revokeObjectURL(url), 100);
};
使用第三方库简化处理
使用file-saver库
bash
npm install file-saver
javascript
import { saveAs } from 'file-saver';
const downloadExcelWithFileSaver = async () => {
try {
const response = await axios.post('/api/download-excel-map');
const { status, body, filename } = response.data;
if (status === 200) {
const byteCharacters = atob(body);
const byteNumbers = new Uint8Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const blob = new Blob([byteNumbers], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
saveAs(blob, filename || 'download.xlsx');
}
} catch (error) {
console.error('下载失败:', error);
}
};
关键注意事项
-
MIME类型:Excel文件的正确MIME类型是:
.xlsx
:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.xls
:application/vnd.ms-excel
-
文件名编码:确保文件名支持中文,使用UTF-8编码
-
文件大小:对于大文件,考虑使用流式传输而不是Base64
-
错误处理:添加完整的错误处理机制
-
用户体验:可以添加下载进度提示和完成通知
这样处理可以确保Excel文件能够正确下载并在本地用Excel程序打开。