引言
在现代 Web 应用开发中,文件下载是一个常见但容易出错的场景。很多开发者在实现导出功能时,往往只关注'能不能下',却忽略了内存占用、文件名编码以及错误处理等细节。本文将通过一个真实的订单导出案例,拆解前后端协作实现文件下载的完整方案,分析常见问题及解决方案,并提供经过生产验证的最佳实践。
需求背景与初始实现
业务需求
我们需要实现一个订单数据导出功能,允许用户将查询结果下载为 Excel 文件。核心要求包括支持按任务 ID 筛选、生成规范的 XLSX 格式、显示友好的下载状态以及记录操作日志。
后端初版实现
一开始,我们习惯性地返回 Resource 对象,让框架自动处理响应。代码逻辑看起来没问题,但在高并发或大数据量下会暴露问题。
@ApiOperation(value = "下载订单列表", notes = "根据条件导出订单数据为 Excel 文件")
@PostMapping("/order-list/download")
public Result<?> downloadTaskOrderExcel(@RequestBody TaskDownLoadRequest taskDownLoadRequest, HttpServletRequest httpRequest) {
try {
// 获取用户 ID 并记录日志
Integer userId = getUserId(taskDownLoadRequest.getTaskId());
logDownloadStart(userId, taskDownLoadRequest.getTaskId());
// 查询订单数据
List<CustomerOrder> orders = queryOrders(taskDownLoadRequest.getTaskId());
if (orders.isEmpty()) {
return Result.error("没有找到符合条件的订单数据");
}
// 生成 Excel 文件
ByteArrayResource resource = generateExcel(orders);
// 构建响应数据
Map<String, Object> data = buildResponseData(resource);
return Result.ok(data);
} catch (Exception e) {
log.error("下载订单列表失败", e);
return Result.error(500, "下载订单数据失败");
}
}
前端初版实现
前端使用 Axios 请求,设置 responseType: 'blob' 后创建临时链接触发下载。这里主要依赖浏览器默认行为处理文件名。
const download = () => {
loading = .({ : });
{
response = commonApi.(
{ : row. },
{ : }
);
filename = ;
disposition = response.[];
(disposition) {
match = disposition.();
(match) filename = (match[]);
}
blob = ([response.], {
:
});
link = .();
link. = ..(blob);
link. = filename;
..(link);
link.();
..(link);
.();
} (e) {
.();
} {
loading.();
}
};


