设计原则
- 支持大数据量场景下的
excel
导出。采用异步导出方式 - 降低
excel
导出时的内存消耗。基于EasyExcel
再次封装,支持excel
定制化 - 统一
excel
导出规范。后端导出接口统一化、前端导出交互组件化,简化开发流程 - 封装公共导出方法,管理导出的整个生命周期。接入方只用关心业务逻辑,且代码复用性高
- 使用自定义线程池异步处理,避免导出占用大量的服务器资源,影响业务接口正常响应
- 引入文件系统。避免因导出文件过大、导出逻辑耗时过长带来的请求超时等的问题
设计思路
- 公共服务提供统一的
excel
导出接口,前端通过统一的接口来导出。统一导出接口会根据导出的url参数转发请求到各业务方的接口实现上,并带上导出参数 - 公共组件包中提供通用异步导出方法。该方法封装查库、数据写入excel、excel文件上传、同步导出状态等逻辑
- 公共服务提供统一的获取导出excel文件下载地址的方法,支持异步轮询下载导出文件
扩展点: 可在通用导出服务中添加导出审计日志
,记录重要敏感数据的导出情况。并且可以添加导出权限控制
、异常告警
等扩展功能。
组件实施方案
1. 公共服务提供统一接口
包括统一的获取sid接口
以及 查询导出状态接口
。
@RestController
@Api(tags = "Excel导出服务")
@RequestMapping("/excels")
public class ExcelController {
@Autowired
private ExcelService excelService;
@ApiOperation(value = "获取sid", notes = "sid为当前excel导出请求唯一标识,用于异步获取导出状态")
@PostMapping("/export/sid")
public BizResponse<String> sid(@RequestParam String url, @ApiParam("导出查询参数") @RequestBody JSONObject params) {
String sid = excelService.getSid(url, params);
return BizResponse.success(sid);
}
@ApiOperation(value = "查询导出状态", notes = "轮询该接口来获取最新的导出状态,轮询时间推荐从1s开始指数型递增")
@PostMapping("/export/status")
public BizResponse<ExportResultResponse> status(@RequestParam String sid) {
ExportResultResponse response = excelService.status(sid);
return BizResponse.success(response);
}
}
2. 公共组件包提供统一导出方法
传统的同步导出方法直接在响应流中返回excel
数据。当导出数据很大,或者导出数据依赖外部服务时,导出会出现请求超时、网关熔断等情况。
public class ExcelUtil {
...
/**
* 导出excel
*
* @param list 数据列表
* @param clazz 导出模型类
* @param filename 导出文件名
* @param response httpServletResponse
* @deprecated 已废弃。推荐使用 {@code exportForSid} 异步导出方法
*/
@Deprecated
public static <T> void export(String filename, Class<T> clazz, List<T> list, HttpServletResponse response) {
try {
filename = URLEncoder.encode(filename, "UTF-8");
} catch (UnsupportedEncodingException e) {
// do nothing here.
}
try (ServletOutputStream outputStream = response.getOutputStream()) {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
response.addHeader("Content-Disposition", "attachment; filename=" + filename + EXPORT_UPLOAD_EXTENSIONS);
EasyExcel.write(outputStream, clazz).sheet(filename).doWrite(list);
} catch (IOException e) {
throw new BizException(BizErrorCodeEnum.OPERATION_FAILED, filename + "导出失败"